背景:
上一篇博文中,在对storescp工具源文件storescp.cc和DcmSCP类的源文件scp.cc进行剖析后,得出了两者都可以实现响应C-ECHO和C-STORE(需要对DcmSCP类进行扩展)请求的功能。但是在对DcmSCP类进行扩展,期望模拟实现自己的storescp.exe工具时遇到了问题,客户端提示服务中断链接,而服务端显示保存失败,如下图所示。此次博文通过排除该问题再一次对storescp.cc和scp.cc进行对比,主要从Presentation Context、AbstractSyntax、TransferSyntax等细节出发,认真学习DICOM通讯服务。
问题排除:
1)对比分析storescp.exe工具包与自定义工具包的调试信息
为了排除storescu.exe客户端的问题,大致确定问题出现的范围,决定再一次用storescp.exe作为客户端,使用storescu.exe对其发送C-STORE请求,查看storescu.exe客户端的调试信息。与我们的自定义工具服务端的调试信息进行对比。
对比上图中的调试信息,其中红色部分END A-ASSOCIATE-AC表示的是服务端已经顺利的与客户端完成了握手,即网络已经顺利响应了客户端的连接。蓝色部分表示客户端发送的C-STORE-RQ请求也已经顺利的到达了服务端,唯一不同的就是黄色圆圈标记的部分,说明在服务端接收到C-STORE-RQ请求后对其处理有问题。而我们自己封装的ZSDcmStoreSCP类对于C-STORE-RQ的处理函数是直接拷贝的storescp.exe工具内的storeSCP和storeSCPCallback函数。因此大致可以确定问题可能出现的范围。
2)单步调试
在handleIncomingCommand函数内部调用storeSCP指令处插入断点,进入单步调试状态。利用VS2012提供的新的调试工具“并行堆栈”,找到了第一个返回状态cond.bad()为true的地方,即函数ASC_findAcceptedPresentationContext,如下图所示:
继续单步调试,进入到ASC_findAcceptedPresentationContext函数内部,发现真正出现错误的地方是findPresentationContextID,该函数的参数presentationContextID始终为零。
猜测:可能是presentationContextID参数在传递过程中的某一环节出错了,导致程序失败。为了验证我们的想法,打开storescp.exe工程,进入调试模式,查看参数的数值情况,如下图所示,storescp.exe工具包中presentationContextID参数的值为41。因此证明了我们在presentationContextID参数传递的过程中发生了错误。
3)参数传递流向
为了确定具体传递过程中的错误环节,我们采取回溯的方式,通过回溯findPresentationContextID函数中presentationContextID参数的来源来确定问题出现的具体位置。
(注:为了方便讲图片旋转了90度,劳烦大家歪着脖子将就着看一下下吧哈)
从上图中可以看出T_ASC_PresentationContextID参数最终的来源是我们重载DcmSCP基类的handleIncomingCommand函数,因此与storescp.exe中的是实现对比,仔细查看该部分的代码,发现了一个重大问题,原来我们在将storescp.exe工具中的processCommands函数内容拆解到handleIncomingCommand函数内部时,将多余的DIMSE_receiveCommand函数注释掉的同时,遗漏了注释掉T_ASC_Association局部变量,使得原本应该通过DIMSE_receiveCommand函数获取的presID变量一直被局部变量覆盖为0——至此问题找到了。
那么由于注释掉了多余的DIMSE_receiveCommand函数,我们从何处来获取presID参数呢。查看一下handleIncomingCommand函数的头发现函数参数中也没有出现T_ASC_PresentationContextID类型的参数。但是对比scp.cc原本处理C-ECHO请求的函数handleECHORequest我们发现,scp.cc类中将presID的值存储在了DcmPresentationContextInfo类型的presInfo变量中。因此我们修改storeSCP的调用,将presInfo.presentationContextID传递进入作为presID的初值。
修改完成后,再次调试发现程序运行正常,客户端顺利收到了服务端反馈回来的DIMSE Message,并且在服务端的目录下也看到了storescu.exe传递过来的dcm文件(当然在storeSCP函数内部根据时间等信息对文件进行了重命名)。
至此上一博文中自模拟storescp.exe工具包的问题已经顺利解决,利用我们自己封装的ZSDcmStoreSCP类可以简单地实现storescp.exe工具包的功能。
总结分析:
此次调试排除错误的过程中发现,在组合移接不同文件的代码时要格外的注意细节部分。顺便借着此次传递丢失T_ASC_Association类型参数的问题,我们详细的分析一下传递失败的T_ASC_Associaton类型参数的作用,为什么简单的一个参数传递失败,就会导致C-STORE功能失效?
1)Presentation Context、AbstractSyntax、TransferSyntax细节学习
在上一篇博文中我们摘录了DICOM3.0标准中关于Presentation Context、AbstractSyntax、TransferSyntax几个名词的解释,通俗一点来讲就是说Presentation Context代表的是两个应用实体(AE=Applicaiton Entity)交互的环境(通常叫做上下文),它包含后面的AbstractSyntax和TransferSyntax名词。AbstractSyntax与TransferSyntax比较容易混淆就是因为英文单词中都包含了Syntax,其实两者完全是不同的概念,AbstractSyntax关注的是上层信息,即两个交互的AE之间
进行的是何种交互,也就是标准中所说的服务对象对(SOP)的类型,通过查看dcuid.h文件可知,AbstractSyntax有Store、Query/Retrive、Worklist等类型,例如Store类型的UID_CTImageStorage 、Query/Retrive类型的UID_FINDPatientRootQueryRetrieveInformationModel、Worklist类型的UID_FINDModalityWorklistInformationModel等等。这几个是我们在C-ECHO、C-STORE、C-FIND服务中常用到的几种;而TransferSyntax关注的是信息传输交互时的编码规则,是Explicit还是Implicit,是LittleEndian还是BigEndian,常见的有UID_LittleEndianImplicitTransferSyntax、UID_LittleEndianExplicitTransferSyntax等等,具体的可以自行查看dcuid.h文件。而上面传递丢失的presID参数就是包括了这两种最重要的交互信息。因此会导致整个C-STORE服务失败。
2)storescp.cc与scp.cc整体再对比
那么storescp.exe工具包和DcmScp类又是在什么时刻?什么位置来设置这些参数的呢?下面我们就再一次对比分析一下两种工具的实现流程(细节可参照上一篇博文DICOM医学图像处理:storescp.exe与storescu.exe源码剖析,学习C-STORE请求)。
2.1 scp.cc源码文件
借助上一篇博文的图片来分析一下DcmSCP类中对于Presentation Context(即AbstractSyntax和TransferSyntax)的具体操作。
上图中给出了DcmSCP类中设置Presentation Context上下文环境的具体位置和使用的函数。
2.2 storescp.exe源码文件
想必storescp.exe工具包中的设置流程也基本是相似的,接下来我们同样借助上一篇博文的图来分析一下。
对比两个图片我们可以发现DcmSCP和storescp.exe都是在处理真正的DIMSE消息之前对连接的Presentation Context上下文进行配置,至于这么多UID(无论是AbstractSyntax还是TransferSyntax都是在dcuid.h文件中用统一的UID来定义的)存储到哪里了呢?结合上一篇博文的分析,我们知道外部循环开始后主要的核心函数都有一个T_ASC_Association类型的参数assoc(storescp.exe中)或m_assoc(DcmSCP类中),查看一下T_ASC_Association类型的定义,并逐级打开,可以发现正如我们预料中的一样,该变量中存储了连接的上下文的所有UID,如下图所示:
至此我们对于DcmSCP类和storescp.exe工具的源码的剖析总算告一段落了,希望大家能够对DICOM的网络通讯服务有一个更深刻的认识。
3)对新版DCMTK中DcmSCP和DcmStoreSCP实现的初步猜想
通过此次使用DcmSCP类的工程发现,该类的设计有些欠妥,开放的接口不够合理,无法轻松的实现扩展来响应C-STORE等其他请求。猜想新版的DCMTK肯定会进行修改,通过查看dcmtk3.6.1的官方说明,发现新版的dcmtk开源库中的确对DcmSCP和DcmSCU基类进行了大的修改,开放了众多新的接口(如下图)方便用户进行扩展,另外新版的库中直接给出了封装好的DcmStoreSCP和DcmStoreSCU类,所以大家就不要使用我给出的代码ZSDcmStoreSCP类啦,仅供测试使用,具体运用是还是赶紧安装最新版的dcmtk开源库吧。
实例工程代码下载:
1)CSDN资源下载:
连接:http://download.csdn.net/detail/zssureqh/7870789,需要1个积分奥。
2)Github免费下载
后续专栏博文预告:
1)dcmtk3.6.0的DcmSCP与dcmtk3.6.1的DcmSCP分析,以及dcmtk3.6.1的DcmStoreSCP和DcmStoreSCU的使用
2)Dcmtk与fo-dicom保存文件的不同设计模式:单线程VS多线程
3)wlmscpfs.exe与findscu.exe的源码剖析:学习C-FIND请求
作者:[email protected]时间:2014-09-13