DICOM:再次剖析fo-dicom中DicomService的自定义事件绑定

题记:

趁着《从0到1》大火的热潮,近期重新翻阅了一遍《从一到无穷大》(这样是不是感觉整个非负数轴就圆满了^_^)。虽然作为科普类书籍,但是里面的内容还是比较深奥,幸亏有作者精准的翻译,一番细细品味后犹如醍醐灌顶,心中透亮。

一直幻想有外星人、宇宙外生物的存在,从《源代码》描述的“平行世界”,到《星际穿越》的“超维空间”,再到时下泛滥的穿越剧,却总未解开心中那团疑惑。或许只有时间的流逝才能给我解答,只怕光阴荏苒,时不我待。遂突发奇想,想模仿大雄坐时空隧道去看看“那年今日”的我。于是从书柜里翻出了上学时的硬盘,找到了那年今天的学习笔记,有种莫名的激动,闭上双眼努力回想‘那时那景’——这程序好难调啊,还有好多书没看,还有好多事要做——
原来我一直如此单调的生活,汗!

背景:

通过寥寥几笔,只可简单回想“那时那景”,但却清晰记得也遇到了奇葩问题,如同今天的‘坑’一样:

在之前的专栏中曾简单介绍过fo-dicom实现各种DIMSE-C服务,简便快捷,诸如fo-dicom网络传输之C-FIND and C-MOVE。今天在结合WCF使用fo-dicom时遇到了一个问题,“多个序列的文件被写入到了同一个文件中,最后生成了一个多大几个G的大文件”。

起初以为是对WCF中实例模式和对象生命周期,即PerCall、PerSession、Singleton,掌握不清,使得将多次客户端调用共用了同一个存储地址。遂阅读了诸多关于这方面的资料,以及C#中的闭包、变量作用域和变量生命周期相关的资料(详情可参见博文最后参考文献章节【1】【2】)。

最后在单步调试时发现,原来是fo-dicom开源库搞的鬼。基于WCF的C-MOVE服务无法实现同时下载多套数据的根源在于fo-dicom中的DicomService服务的绑定采用的是类的绑定,因此其对于CStoreRequest的事件只能绑定到类一级中。而我们此刻实际的需求是“要根据不同的dicom文件存储到不同的位置,且该位置信息通过dicom文件内部自有信息无法构造”。之前错误的将文件存储信息通过“闭包”【3】的形式传递进了DicomService类绑定函数中,此刻绑定到类的DicomService服务与闭包封送的绑定到对象的存储路径之间出现了矛盾,这也就是最终导致多个dcm序列存储到同一个大文件中的问题。

问题剖析:

fo-dicom中DicomServer服务绑定分析:

在DicomServer.cs文件中,对于实际DICOM服务的绑定放在OnAcceptTcpClient函数中,具体代码如下:

private void OnAcceptTcpClient(IAsyncResult result) {
try {
    if (_isDisposing || _listener == null)
        return;
    var client = _listener.EndAcceptTcpClient(result);
    if (Options != null)
        client.NoDelay = Options.TcpNoDelay;
    else
        client.NoDelay = DicomServiceOptions.Default.TcpNoDelay;
    Stream stream = client.GetStream();
    if (_cert != null) {
        var ssl = new SslStream(stream, false);
        ssl.AuthenticateAsServer(_cert, false, SslProtocols.Tls, false);
        stream = ssl;
        }
    T scp = (T)Activator.CreateInstance(typeof(T), stream, Logger);
    if (Options != null)
        scp.Options = Options;
        _clients.Add(scp);
    } catch (Exception e) {
        if (Logger == null)
            Logger = LogManager.Default.GetLogger("Dicom.Network");
            Logger.Error("Exception accepting client: " + e.ToString());
    } finally {
        if (!_isDisposing && _listener != null)
            _listener.BeginAcceptTcpClient(OnAcceptTcpClient, null);
    }
}

在利用(T)Activator.CreateInstance(typeof(T),stream.Logger);创建完DicomService服务对象scp后,DicomServer并未留有接口对scp对象添加任何绑定。因此要想将自定义的扩展传递给DicomServer中的DicomService对象,只能使用类级别的静态事件绑定。如之前专栏博文fo-dicom网络传输之C-FIND and C-MOVE中的示例,代码如下所示:

public static OnCStoreRequestCallback OnCStoreRequestCallBack;
public DicomCStoreResponse OnCStoreRequest(DicomCStoreRequest request)
{
    //to do yourself
    //实现自定义的存储方案
    if (OnCStoreRequestCallBack != null)
    {
        return OnCStoreRequestCallBack(request);
    }
    return new DicomCStoreResponse(request, DicomStatus.NoSuchActionType);
}

由于OnCStoreRequestCallback绑定到CStoreSCP类一级中,因此在CMoveSCP启动后,每次C-MOVE-RQ触发本地C-STORE时刻,新绑定的OnCStoreRequestCallBack会自动覆盖之前的绑定。

WCF中实例模式和对像生命周期:

参照资料【1】中的示意图,WCF的实例模型有Per Call、Per Session、Singleton三种,如下图:

三种不同实例模式所对应的是WCF的实例对象的生命周期,即当WCF客户端发起请求时,针对该请求是如何创建WCF服务端实例对象的,但是由于WCF底层并不提供DICOM服务,因此无论采用何种WCF实例模式,最终调用的都是fo-dicom提供的DICOM服务,来此WCF客户端的异步请求具体的流程如下图:

问题解决:

按照上述的分析,导致博文前面提到的奇葩问题的根源是在fo-dicom的DicomServer服务中创建的派生自DicomService的对象只有一个,而且其事件绑定采用的是静态事件绑定,基于类层级的。一旦设置事件绑定,直到终止服务为止,该事件一直有效。即使修改fo-dicom中DicomServer底层源码,将对DicomService及其派生类的事件绑定改成基于对象的,也无法解决该问题。原因是DicomServer的开启需要绑定到端口,而正常情况下一个端口只能绑定一个应用,因此无法创建多个DicomServer对象绑定到同一个端口。

那么到底如何解决问题,实现现实中的奇葩需求呢?我这里采用了一种笨办法,如下图:

1) 在DicomServer服务类中添加一个全局Hast表,在WCF服务端接收到来自客户端的C-MOVE请求,且还未转发到DicomServer之前,将与请求相关的特殊需求保存到HastTable全局表中;无论WCF是采用异步还是同步模式,在HashTable表中都存储了与每个需求对应的特殊变量;

2) 当WCF服务端将需求转发到实际的DicomServer时,DicomServer类绑定的事件内部会读取HastTable中的数据来进行特定处理。

3) 当WCF请求处理完成后,再将之前插入到HashTable中的特定数据清除,以便循环利用HastTable全局表。

至此针对不同请求,进行不同处理的问题就解决了。

参考资料:

【1】 http://www.codeproject.com/Articles/188749/WCF-Sessions-Brief-Introduction

【2】 http://www.cnblogs.com/webglcn/archive/2012/05/02/2479873.html

【3】 http://www.cnblogs.com/frankfang/archive/2011/08/03/2125663.html

作者:[email protected]

时间:2015-06-04

时间: 06-04

DICOM:再次剖析fo-dicom中DicomService的自定义事件绑定的相关文章

HTML中动态生成内容的事件绑定问题【转载】

转自 http://www.hitoy.org/event-binding-problem-of-dynamically-generated-content.html 由于实际的需要,有时需要往网页中动态的插入HTML内容,并在插入的节点中绑定事件处理函数.我们知道,用Javascript向HTML文档中插入内容,有两种方法,一种是在写HTML代码写入JS,然后插入到文档中:另外一种是通过ajax的方式,从服务器获取数据,然后用js把获取的数据经过处理后插入文档中:两种方法各有特点,本文将分析新

Vue事件在组件中的简单使用以及子组件事件触发父组件自定义事件

在Vue中,通过v-on 来监听DOM事件,可以通过@简写代替. 一个简单的在组件中的事件调用示例 在template的Html中使用v-on或@监听一个click事件,并指定事件执行handleClick方法. handleClick需要在组件的methods集合中定义. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta

DICOM:再次剖析fo-dicom中DicomService的自己定义事件绑定

题记: 趁着<从0到1>大火的热潮,最近又一次翻阅了一遍<从一到无穷大>(这样是不是感觉整个非负数轴就圆满了^_^). 尽管作为科普类书籍.可是里面的内容还是比較深奥,幸亏有作者精准的翻译,一番细细品味后宛如醍醐灌顶,心中透亮. 一直幻想有外星人.宇宙外生物的存在,从<源代码>描写叙述的"平行世界",到<星际穿越>的"超维空间",再到时下泛滥的穿越剧,却总未解开心中那团疑惑. 也许仅仅有时间的流逝才干给我解答,仅仅怕光

DICOM:剖析Orthanc中的Web Server,Mongoose之 Flag bit &amp; Event(三)

背景: Orthanc是本专栏中介绍过的一款新型DICOM服务器,具有轻量级.支持REST的特性,可将任意运行Windows和Linux系统的计算机变成DICOM服务器,即miniPACS.Orthanc内嵌多种模块,数据库管理简单,且不依赖于第三方软件.因此通过剖析Orthanc源码可以学习到搭建DICOM系统中的各个环节,例如SQLite嵌入型数据库.GoogleLog日志库.DCMTK医学DICOM库,以及近期要介绍的开源Web Server,Mongoose. 上一篇博文中简单的分析了M

DICOM:深入剖析Orthanc的SQLite,了解WADO&RESTful API

背景: 上一篇博文简单翻译了Orthanc官网给出的CodeProject上"利用Orthanc Plugin SDK开发WADO插件"的博文,其中提到了Orthanc从0.8.0版本之后支持快速查询,而原本的WADO请求需要是直接借助于Orthanc内部的REST API逐级定位.那么为什么之前的Orthanc必须要逐级来定位WADO请求的Instance呢?新版本中又是如何进行改进的呢?此篇博文通过分析Orthanc内嵌的SQLite数据库,来剖析Orthanc的RESTful A

DICOM医学图像处理:DICOM网络传输

背景: 专栏取名为DICOM医学图像处理原因是:博主是从医学图像处理算法研究时开始接触DICOM协议的.当初认识有局限性,认为DICOM只是一个简单的文件格式约定,简而言之,我当时认为DICOM协议就是扩展名为DCM文件的格式说明.其实不然,随着对医疗行业的深入,对DICOM协议也有了更全面的认识.而今才发现DCM文件只是DICOM协议一部分中的一小节,仅仅是整个协议中的一个数据结构,而DICOM协议更多的是关于医疗行业各种服务及相关流程的约定,因此其实DICOM协议中最主要的是信息流,是对医院

DICOM医学图像处理:DICOM存储操作之 “多幅JPG图像数据存入DCM文件”

背景: 续上篇,继续介绍如何将多幅JPG图像数据存入DCM文件.即将有损压缩数据直接写入DCM文件,存储为Multi-frame形式. 多幅JPG图像数据存入DCM文件: 为了避免引起歧义,这里着重说明一下.本博文的描述的场景是:假设我们手中有多张JPG文件,想把JPG文件写入DCM文件,即单个DCM文件包含多幅图像信息的Multi-Frame形式.该问题之前与CSDN博友y317215133y也讨论过,当时我在OFFIS论坛中找到了一个帖子直接给了y317215133y答复.今天重新梳理了一下

深入剖析Java编程中的中文问题及建议最优解决方法

摘录自:http://fafeng.blogbus.com/logs/3062998.html http://www.blogbus.com/fafeng-logs/3063006.html 深入剖析Java编程中的中文问题及建议最优解决方法 说明:本文为作者原创,作者联系地址为:[email protected].由于Java编程中的中文问题是一个老生常谈的问题,在阅读了许多关于Java中文问题解决方法之后,结合作者的编程实践,我发现过去谈的许多方法都不能清晰地说明问题及解决问题,尤其是跨平台

boost.asio源码剖析(四) ---- asio中的泛型概念(concepts)

* Protocol(通信协议) Protocol,是asio在网络编程方面最重要的一个concept.在第一章中的levelX类图中可以看到,所有提供网络相关功能的服务和I/O对象都需要Protocol来确定一些细节. Protocol的约束摘要如下: 1 class protocol 2 { 3 public: 4 /// Obtain an identifier for the type of the protocol. 5 int type() const; 6 7 /// Obtain