背景:

之前专栏中介绍最多的两款PACS各自是基于dcmtk的dcmqrscp以及Orthanc。和基于fo-dicom的DicomService(自己开发的)。该类应用场景都是针对于局域网,因此在使用DIMSE-C各项服务时并未遇到的复杂问题,学习和使用成本相对较低。

通过近一年的时间也已经对C-ECHO、C-FIND、C-STORE、C-MOVE、N-PRINT等各项服务都进行了详细介绍,而且从DICOM标准来看C-MOVE服务能够包括C-GET,因此今天之前专栏中从未介绍过C-GET服务。

最近因为项目须要,開始频繁接触基于dcm4che的dcm4chee服务。通过托管于JBoss AS应用server中dcm4chee能够为互联网用户提供DICOM服务(注意差别于之前介绍的Orthanc的RESTful服务,以及DICOM标准中的WADO协议)。

通过直接将DICOM服务部署到外网主机中来实现web的PACS服务,因为DICOM协议本身是建立在TCP上,所以从理论上这样的DICOM服务的web化是可行的。

起初在对接C-FIND、C-STORE等服务时也非常顺利,与常规的局域网操作全然同样。无非就是訪问的对端AE节点IP地址是公网IP而已。可是在进行C-MOVE操作时,却总是无法成功。

dcm4chee的AE Title配置服务:

起初以为dcm4chee对C-MOVE的实现有问题。于是自己在本地Ubuntu虚拟机中搭建部署了一套dcm4chee服务。

通过单步调试,以及检索dcm4che官网,找到了部分线索:dcm4che的AE Title Configuration Service

如官网中所述。dcm4chee因为部署到互联网中,相较于局域网须要配置SCU和SCP各自AETitle信息的情形,dcm4chee额外加入了一种自己主动配置模式,即假设请求端(即不论什么SCU)开放的port是104或11112。dcm4chee会自己主动将该SCUclient的AETitle加入到配置中,同意其与dcm4chee建立连接。依照官方说明。改动请求端的port为11112,再次尝试。

C-FIND依旧能够返回结果。而C-MOVE却始终无法下载数据到本地。通过查看dcm4chee后台日志发现,总是提示无法建立连接。那么究竟是不是dcm4chee服务出的问题呢?。通过使用dcm4che源代码中自带的工具包dcmqr.bat发现能够顺利从部署到外网的dcm4cheeserver中下载数据到本地,详细操作指令例如以下:

dcmqr DCM4CHEE@XXX.XXX.XXX.XXX:11112 -cget -nocfind -q0020000D=1.3.6.1.4.1.30071.6.10987654321.1234567890 -cstore CT -cstoredest c:\test

输出日志截图例如以下:



在本地c:\test文件夹下能够看到下载成功的dcm文件。

C-GET与C-MOVE:

既然dcmqr.bat工具能够顺利将数据下载到本地,能够确信dcm4chee服务端实现没有问题。

那么究竟是哪里出现了问题呢?让我们接着往下看:

同样使用dcmqr.bat工具,输入下述指令:

dcmqr DCM4CHEE@XXX.XXX.XXX.XXX:11112 -cmove ZSSURE -nocfind -q0020000D=1.3.6.1.4.1.30071.6.198690283870599.4896581024566519 -cstore CT -cstoredest c:\test

却总是提示无法识别Move Destination AETitle:ZSSURE,例如以下截图所看到的各自是dcm4chee服务端和dcmqr.batclient的额输出日志:



至此应该能够大致确定问题出如今C-MOVE服务的实现机制上,通过dcmqr.bat工具的測试。能够确定C-GET服务能够顺利从部署到互联网的dcm4cheeserver下载数据,而C-MOVE服务无法实现。



以下就详细分析一下C-GET与C-MOVE的差别:



从DICOM3.0标准官方描写叙述中能够看出,两者的目的是同样的。且主动发起请求方(即SCU)的起始操作是同样的——都是从SCP端查询匹配项。

可是随后的子操作不同。在C-GET SERVICE中的描写叙述是子操作的触发在同一个连接中(on the same Association);而在C-MOVE SERVICE中的描写叙述是子操作的触发在单独的、另外的一个连接中(on a separate Association)

为了更清楚的描写叙述两者的差别,请看以下两幅示意图:



图中用双向箭头来示意详细C-GET和C-MOVE服务中所建立的TCP连接数;单项箭头分别表示详细服务中的RQ和RSP,依照官方说法C-GET和C-MOVE服务都是确认服务(Confirmed Service),所以单项箭头都是一一配对的,正常情况下一个RQ相应一个RSP;单项箭头标记的数字表明C-GET和C-MOVE服务详细请求过程中的各消息发送时序。

fo-dicom(mDCM)实现C-GET:

之前分析过fo-dicom(mDCM)库中实现DICOM协议的各种关系。简单来说类就是对某种操作以及操作所需的数据的一种封装,从上面的结构图分析以及官方DICOM3.0标准来看,DICOM协议中的操作主要就是建立&关闭TCP连接、发送&接收请求、读取&解析套接字信息(DIMSE)。以及相关异常处理等等,所以总体的操作都被放到了DcmNetworkBase类中。然后依据不同的角色(即SCU和SCP)。分别派生出DcmServiceBase和DcmClientBase,直至最底层的DcmServer和DcmClient。

简单的类图例如以下所看到的:

依据背景中的描写叙述,我们这里扮演的是C-GET SCU角色。即须要派生DcmClient。细致查看fo-dicom(mDCM)源代码发现当中并未实现C-GET相关的不论什么client(SCU)和服务端(SCP)类的封装。为了借鉴现有经验又查看了一下dcmtk和dcm4che的源代码,仅仅有dcm4che提供的dcmqr.bat工具包中嵌入了c-get服务。

通过查看DICOM标准第7部分可知,C-GET服务于C-MOVE服务除了在Association上不同以外。其它參数以及操作流程差点儿全然同样。

既然fo-dicom(mDCM)中给出了CMoveClient类,那么我么直接借用CMoveClient类来编写CGetClient类。

起初直接拷贝了CMoveClient类源代码。去掉了当中关于第三方DestinationAE的相关成员变量,在測试过程中总是出现Association.Available==0的情况,例如以下图所看到的:



后来通过对照DICOM标准以及上述分析发现。在CMoveClient类中并未实现DcmNetworkBase类的OnReceiveCStoreRequest方法,而C-GET服务依照我们之前的分析须要在同一个Association中处理来C-GET SCP的C-STORE SCU,因此对CGetClient类的OnReceiveCStoreRequest进行扩展。加入保存DcmDataset到.dcm文件的代码(注:这里为了方便用户后期扩展,将详细的实现放到了代理delegate中。由用户在Dicom.dll库外部自己定义实现)
大致的代码例如以下所看到的:

        protected override void OnReceiveCStoreRequest(byte presentationID, ushort messageID, DicomUID affectedInstance,
DcmPriority priority, string moveAE, ushort moveMessageID, DcmDataset dataset, string fileName)
{
try
{
if (OnCStoreRequest != null)
OnCStoreRequest(presentationID, messageID, affectedInstance, priority, moveAE, moveMessageID, dataset, fileName);
SendCStoreResponse(presentationID, messageID, affectedInstance, DcmStatus.Success); }
catch (System.Exception ex)
{
SendCStoreResponse(presentationID, messageID, affectedInstance, DcmStatus.ProcessingFailure); }
Console.WriteLine("c-move c-store RQ!");
}

加入OnReceiveCStoreRequest实现代码后。再一次连接dcm4cheeserver后能够看到顺利接收到了DcmDataset并保存到本地.dcm文件。

至此CGetClient类的封装工作就完毕了。

知识点补充:

TCP全双工:

全双工协议(即Full-Duplex Transmissions),是指通信时同意两个方向同一时候传递数据,他相当于两个单工通信。它要求发送设备和接收设备都有独立的接收和发送能力。

通熟点来讲在通信时能保证在随意时刻两方都能听到对方的声音。(摘自《全双工和半双工的差别》)。TCP协议就是全双工协议,因此在上述实现C-GET请求时能够再同一个TCP连接中(即Association)实现C-STORE和C-GET。

外网VS内网:

IP地址资源紧张,255.255.255.255形式的32位地址已经远不能标识互联网中的全部主机。而外网和内网的划分在一定程度上攻克了IP地址资源紧张的问题。每一个内网通过路由映射到一个外网IP地址,内网中的主机经过路由器之后才干訪问外网,而对外他们仅仅耗费了一个外网IP地址资源。

常见的内网IP有以下几种类型:

10.0.0.0~10.255.255.255

172.16.0.0~172.31.255.255

192.168..0.0~192.168.255.255


所以仅仅要拥有外网IP。就能够直接连接互联网,而不须要经过路由器或交换机。因为外网IP资源宝贵,且属于全球化资源,因此外网IP一般都是用于公司企业、学校、政府等机构。除了上述三类内网IP以外的地址都是外网IP

外网訪问内网:

自媒体的火热,以及Github、Gitlib等的普及,人人都希望建立自己的独立博客、独立站点。

之前博文介绍的就是一种公布个人站点的一种方式。这样的方式费用较高,须要购买server(拥有外网IP的主机),能够简单的觉得就是购买外网IP地址,以及域名。大家能够訪问www.zssure.me体验一下我搭建的个人主页。

那么除此以外有没有其它方法能够公布个人主页呢?搜索一下就能够看到非常多博文介绍怎样使用路由的虚拟server(即port映射)来实现外网訪问内网的功能呢,如是也能够简单的公布个人主页。当然这样的方式的危急系数高,易受到黑客攻击,不建议大家使用。

关于从外网直接訪问内网的设置可參见路由器实现外网訪问内网

大致的流程是利用路由器的虚拟server功能,进行port映射,实现外网IP地址到内网IP+port的一一映射。从而实现外网直接訪问内网的目的。示意图例如以下(图片来源于路由器实现外网訪问内网):

内网訪问外网:

内网訪问外网的情况就比較常见。诸如公司、学校等机构局域网内能够上网的都是内网訪问外网。通常通过NAT(Network Address Translation)来实现。百度百科对NAT的描写叙述例如以下:

NAT(Network Address Translation,网络地址转换)是1994年提出的。当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址),但如今又想和因特网上的主机通信(并不须要加密)时。可使用NAT方法。

这样的方法须要在专用网连接到因特网的路由器上安装NAT软件。

装有NAT软件的路由器叫做NAT路由器,它至少有一个有效的外部全球IP地址。这样,全部使用本地地址的主机在和外界通信时,都要在NAT路由器上将其本地地址转换成全球IP地址。才干和因特网连接。

另外。这样的通过使用少量的公有IP 地址代表较多的私有IP 地址的方式,将有助于减缓可用的IP地址空间的枯竭。

在RFC-1632中有对NAT的说明。

利用NAT有两大优点。各自是宽带分享和安全防护。

对内能够多台主机共享一条宽带。对外多带主机相应一个公网IP,因此当受到外网攻击时无法确定相应的单一主机。(这也就是C-MOVE请求訪问基于WEB的dcm4cheeserver终于失败的原因。

虚拟server:

VPN:

对于虚拟server和VPN的介绍临时搁置一下,因为后期须要手动搭建VPN服务因此放到下一篇博文中再介绍。敬请期待!

PS:能够对照之前专栏中的博文DICOM医学图像处理:AETitle在C-FIND和C-MOVE请求中的设置问题来分析一下内网与外网的差别。这里特此说明当时的一种解决方式(即利用TcpClient来直接反推IP地址)的做法是有局限性的,在局域网中是可行的,在互联网大环境下会失败。

修正:博文中C-GET服务示意图中C-GET-RSP、C-STORE-RQ、C-STORE-RSP等消息的流程是错误的,详情能够參见兴许的博文DICOM:C-GET服务

作者:zssure@163.com

时间:2015-07-13

最新文章

  1. 当Eclipse报版本低时的处理方法
  2. [LeetCode] Find the Duplicate Number 寻找重复数
  3. Let it go.Let it be.Keep it up!
  4. word多级编号,如何让第一级为大写“一”,其他级别均为小写1.
  5. hdu 5818 (优先队列) Joint Stacks
  6. andriod终端操作命令
  7. Android 完美退出 App (Exit)
  8. Android 图标上面添加提醒(二)使用开源UI类库 Viewbadger
  9. 简单RTP发送类c++实现
  10. JBoss + EJB3 + MySql : 开发第一个EJB
  11. js 上下切换图片
  12. linux(centos 7)学习之 ~目录下的文件anaconda-ks.cfg
  13. 排序1,2......n的无序数组,时间复杂度为o(n),空间复杂度为o(1)
  14. P1742 最小圆覆盖(计算几何)
  15. [转]Jmeter + Grafana + InfluxDB 性能测试监控
  16. Pycharm下面出现No R interpreter defined
  17. 【AI】PaddlePaddle-Docker运行
  18. MySQL 误操作后数据恢复(update,delete忘加where条件)
  19. Django学习笔记之模板
  20. python windows 安装sklearn

热门文章

  1. 新手使用ThinkPHP3.2.3的命名空间问题
  2. Android OpenGL库加载过程源码分析
  3. git commit -am 合并操作
  4. SEO 外链 内链 的定义
  5. javascript 阻止多次点击造成的轮播混乱
  6. 新浪授权认证(不用SDK)
  7. Webfrom基础知识
  8. java菜鸟篇<一> 对JsonObject 和JsonArray知识点理解
  9. for()循环
  10. 一台nginx服务器多域名配置 (转)