zoukankan      html  css  js  c++  java
  • 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的开启须要绑定到port,而正常情况下一个port仅仅能绑定一个应用,因此无法创建多个DicomServer对象绑定到同一个port。


    那么究竟怎样解决这个问题,实现现实中的奇葩需求呢?我这里採用了一种笨办法。例如以下图:
    这里写图片描写叙述

    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


    作者:zssure@163.com
    时间:2015-06-04

  • 相关阅读:
    idea添加自定义插件仓库 灵狐插件、阿里代码规约插件安装&idea插件推荐
    常用版本名称含义:SNAPSHOT->alpha->beta->release->GA等
    Ambari Centos7离线安装教程详细指导(参考)
    JDK11变化详解&JDK8升级JDK11详细指南
    idea jdk8、jdk9、jdk11、jdk12并自由切换
    idea java EclipseFormatter代码格式化模板
    java基础对象浅复制和深复制(基础知识)
    斐讯路由器L(联)B(壁)K-码兑换包安全下车通道(图文教程)
    SipDroid +miniSIPServer搭建SIP局域网语音通话(一)
    Android Studio 找不到EventBus/ButterKnife等第三方包解决方案
  • 原文地址:https://www.cnblogs.com/clnchanpin/p/7150025.html
Copyright © 2011-2022 走看看