题记:

趁着《从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;elseclient.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

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

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

  1. 如何在Google Analytics(分析)中轻松设置自定义事件跟踪

    by Pankaj Singh 潘卡·辛格(Pankaj Singh) 如何在Google Analytics(分析)中轻松设置自定义事件跟踪 (How to easily set up custom ...

  2. vba 定义类_工作表中如何响应自定义事件

    大家好,在上两讲中讲了类模块的调试行为,对于我们写代码人员来讲,要充分地理解每个知识点的概念,才能在实际工作中灵活运用,在我的第一套教程<VBA代码解决方案>中,我推出的是一种积木编程的思 ...

  3. magix中的2种事件绑定方式

    可操作html与js magix采用html与js文件分离的写法,当我们在阅读时,希望能直观的看出节点绑定了哪些事件或这个函数是为哪种事件服务的. 如下html <div><butt ...

  4. 【Android游戏开发之七】(游戏开发中需要的样式)再次剖析游戏开发中对SurfaceView中添加组件方案!...

    本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/android-game/308.html   ...

  5. Windows Phone DataBound ListBox中针对UIElement的事件绑定(Button)

    在很多使用DataBound的ListBox案例中,我们都监听了它的 SelectionChanged 事件,当我们用手指点击某一项时,可以从 ListBox.SelectedItem 属性上很容易获 ...

  6. $.ligerdialog.open中确定按钮加事件_彻底搞懂JavaScript中的this指向问题

    JavaScript中的this是让很多开发者头疼的地方,而this关键字又是一个非常重要的语法点.毫不夸张地说,不理解它的含义,大部分开发任务都无法完成. 想要理解this,你可以先记住以下两点: ...

  7. DICOM:剖析Orthanc中的Web Server,Mongoose之 Flag bit Event(三)

    背景: Orthanc是本专栏中介绍过的一款新型DICOM服务器,具有轻量级.支持REST的特性,可将任意运行Windows和Linux系统的计算机变成DICOM服务器,即miniPACS.Ortha ...

  8. 【转】DICOM文件格式剖析(初识)

    转自:DICOM文件格式剖析(初识)_MoreThinker的博客-CSDN博客_dicom格式 初识DICOM(适合初学者) 初识DICOM文件,发现网上的资料有点少,大部分的资料都不全,在这里做一 ...

  9. GDCM:转储一个DICOM文件,显示DICOM中的结构和值的测试程序

    GDCM:转储一个DICOM文件,显示DICOM中的结构和值 GDCM:转储一个DICOM文件,显示DICOM中的结构和值 GDCM:转储一个DICOM文件,显示DICOM中的结构和值 #includ ...

最新文章

  1. oracle sqlplus 常用命令大全
  2. 如何在 Outlook 2003、Outlook 2007 中管理pst 文件
  3. Centos6.4_X64飞信安装
  4. MySQL 数据库的操作 连接、新增、删除、选择数据库 命令行(带图)
  5. Python瓦匠 —— 正则表达式(五)
  6. Codeforces 845 A. Chess Tourney 思路:简单逻辑题
  7. X3D.Studio编辑器界面介绍
  8. 【动态规划】cf1034C. Region Separation
  9. Mozilla 开源支持计划:首批捐助 7 开源项目 50 万美元
  10. tensorflow contrib模块_OpenCV DNN 模块-风格迁移
  11. php gd实现简单图片验证码与图片背景文字水印
  12. Linux--vmlinuz、vmlinux、initrd
  13. 【交换机在江湖】第十四章 VLAN通信篇
  14. switch语句的ns图怎么画_switch语句流程图 c语言中swtich怎么画流程图
  15. Win10右键菜单管理工具
  16. 基于SpringBoot的车辆调度系统的设计与实现
  17. CAD输出为PDF至A4大小
  18. 关于sim/usim的一些概念
  19. 逆分布函数法生成随机数(以指数分布和双指数分布为例)
  20. 如何做好用户故事地图?

热门文章

  1. Build RESTful APIs with Spring MVC
  2. 有糖UTOUU网赚攻略
  3. python人物抠图算法_比PS还好用!Python 20行代码批量抠图
  4. C++坦克大战(新手)
  5. 团体程序设计天梯赛-练习集 L1
  6. 四川大学和电子科技大学计算机哪个好,四川大学和电子科技大学哪个好?高校大比拼开始!...
  7. tga怎么转成png格式?
  8. android studio 使用 jni 编译 opencv 完整实例 之 图像边缘检测!
  9. 线性筛求莫比乌斯函数前缀和
  10. LVGL之学习篇(一)