背景:

之前博文对照过多次C-MOVE与C-GET服务的差别,两者最大的差别在于C-GET是基于单个TCP连接的点对点的双方服务。而C-MOVE是基于两个TCP连接的三方服务(详情參见:《DICOM:C-GET与C-MOVE对照剖析》。以及DICOM:C-GET与C-MOVE对照剖析(续))。

加之前一篇专栏博文DICOM:DICOM3.0网络通信协议之“开源库实现剖析”也已具体对照了dcm4che和fo-dicom开源库的底层实现,因此本篇博文直接给出基于fo-dicom开源库的C-GET服务实现的主要代码,着重介绍C-GET服务端与C-MOVE服务端发起C-STORE 子操作的差别。

C-GET-SCU:

在fo-dicom开源库中DICOM的各种Client端已经抽象出了DicomClientBase类,针对各种DIMSE-C服务(诸如C-STORE、C-GET、C-MOVE、C-ECHO、C-FIND)唯一不同的就是绑定各自相应的托付就可以。

C-GET-SCUclient的核心代码例如以下:

        #region Protected Overrides
protected override void OnConnected()
{
DcmAssociate associate = new DcmAssociate(); byte pcid = associate.AddPresentationContext(_getSopClass); associate.AddTransferSyntax(pcid, DicomTransferSyntax.ExplicitVRLittleEndian);
associate.AddTransferSyntax(pcid, DicomTransferSyntax.ImplicitVRLittleEndian);
byte pcid2 = associate.AddPresentationContext(DicomUID.CTImageStorage); associate.AddTransferSyntax(pcid2, DicomTransferSyntax.ExplicitVRLittleEndian);
associate.AddTransferSyntax(pcid2, DicomTransferSyntax.ImplicitVRLittleEndian); associate.CalledAE = CalledAE;
associate.CallingAE = CallingAE;
associate.MaximumPduLength = MaxPduSize;
//zssure:2015/07/06
//Add UserIdentity Information
//http://medical.nema.org/medical/dicom/current/output/html/part07.html#sect_D.3.3.7
if (userIdentity == null)
SendAssociateRequest(associate);
else
SendAssociateRequest(associate, userIdentity);
//zssure:end,2015/07/06
} private void PerformQueryOrRelease()
{
if (_getQueries.Count > 0)
{
byte pcid = Associate.FindAbstractSyntax(GetSopClassUID);
if (Associate.GetPresentationContextResult(pcid) == DcmPresContextResult.Accept)
{
current = _getQueries.Dequeue();
SendCGetRequest(pcid,1,Priority,current.ToDataset());
}
else
{
SendReleaseRequest();
}
}
else
{
SendReleaseRequest();
}
}
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-get c-store RQ!");
}
protected override void OnReceiveAssociateAccept(DcmAssociate association)
{
PerformQueryOrRelease();
}
protected override void OnReceiveCGetResponse(byte presentationID, ushort messageID, DcmDataset dataset,
DcmStatus status, ushort remain, ushort complete, ushort warning, ushort failure)
{
if (OnCGetResponse != null)
{
OnCGetResponse(current, dataset, status, remain, complete, warning, failure);
}
if (remain == 0 && status != DcmStatus.Pending)
{
PerformQueryOrRelease();
}
}

【注意】:这里须要注意的有几点:
1)CGETClient端须要响应服务端发起的C-STORE-RQ。因此须要重写OnReceiveCStoreRequest函数;
2)之前在博文DICOM:參考dcm4che2扩展fo-dicom(mDCM)中的UserIdentity字段已经介绍过扩展Association加入UserIdentity字段

C-GET-SCP:

C-GET服务端差别于C-MOVE服务端在于,DicomService服务类自身须要实现OnReceiveCStoreResponse函数,而之前C-MOVE服务端是在发送C-STORE-RQ时直接绑定OnReceiveCStoreResponse事件到CStoreClient。核心代码例如以下:

        private ConcurrentDictionary<ushort, CGetParameters> cgetProcessDic = new ConcurrentDictionary<ushort, CGetParameters>();
protected override void OnReceiveCStoreResponse(byte presentationID, ushort messageIdRespondedTo, DicomUID affectedInstance, DcmStatus status)
{
CGetParameters cgetPara = null;
if (status == DcmStatus.Success)
{
try
{
cgetProcessDic.TryGetValue(messageIdRespondedTo, out cgetPara);
cgetPara.CGetStatus.Complete++;
cgetPara.CGetStatus.Remain--;
SendCGetResponse(presentationID, messageIdRespondedTo, DcmStatus.Pending, cgetPara.CGetStatus.Remain, cgetPara.CGetStatus.Complete, cgetPara.CGetStatus.Warning, cgetPara.CGetStatus.Fail);
if (cgetPara.CGetStatus.Remain > 0)
{
///self do something
}
else
{
cgetPara.CGetStatus.Fail++;
cgetPara.CGetStatus.Remain--;
SendCGetResponse(presentationID, messageIdRespondedTo, DcmStatus.Pending, cgetPara.CGetStatus.Remain, cgetPara.CGetStatus.Complete, cgetPara.CGetStatus.Warning, cgetPara.CGetStatus.Fail);
}
}
else if (cgetPara.CGetStatus.Remain == 0)
{
if (cgetProcessDic.TryRemove(messageIdRespondedTo, out cgetPara))
SendCGetResponse(presentationID, messageIdRespondedTo, DcmStatus.Success, cgetPara.CGetStatus.Remain, cgetPara.CGetStatus.Complete, cgetPara.CGetStatus.Warning, cgetPara.CGetStatus.Fail);
else
{
Log.Info("ReceiveCStoreResponse for CGet failed when remove from ConcurrentDictionary<ushort, CGetParameters>");
try
{
cgetProcessDic.TryRemove(messageIdRespondedTo, out cgetPara);
}
catch (System.Exception ex2)
{
Log.Info("ReceiveCStoreResponse for CGet failed when remove from ConcurrentDictionary<ushort, CGetParameters> again,{0},{1}", ex2.Message, ex2.StackTrace);
}
} }
}
catch (System.Exception ex)
{
Log.Info("ReceiveCStoreResponse for CGet failed! {0},{1}", ex.Message, ex.StackTrace);
SendCGetResponse(presentationID, messageIdRespondedTo, DcmStatus.InvalidArgumentValue, cgetPara.CGetStatus.Complete, cgetPara.CGetStatus.Remain, cgetPara.CGetStatus.Warning, cgetPara.CGetStatus.Fail);
}
}
else
{
cgetPara.CGetStatus.Fail++;
cgetPara.CGetStatus.Remain--;
SendCGetResponse(presentationID, messageIdRespondedTo, DcmStatus.Pending, cgetPara.CGetStatus.Remain, cgetPara.CGetStatus.Complete, cgetPara.CGetStatus.Warning, cgetPara.CGetStatus.Fail);
}
}

【注意】:上述代码须要注意是:

通过线程安全集合类ConcurrentDictionary在C-GET与C-STORE两种服务间同步状态,由于在OnReceiveCGetRequest函数中服务端是能够明白定位client请求的数据的,可是在接收到clientC-STORE-RSP时,通过简单的DICOM Message是无法得知之前在OnReceiveCGetRequest中定位的数据的,因此须要在服务类中加入一个线程安全集合类来共享状态。如是可见,上述代码中大量的操作是在维护ConcurrentDictionary的状态,用于协调C-STORE与C-MOVE在同一个TCP连接中消息的传递。

备注:

这里纠正之前博文DICOM:C-GET与C-MOVE对照剖析中对于C-GET服务的C-STORE和C-MOVE消息流的流程错误。例如以下图所看到的:

作者:zssure@163.com

时间:2015-12-16

DICOM:C-GET服务的更多相关文章

  1. DICOM的Worklist服务

    看 DICOM 标准有一段时间了,前面几篇也介绍了一下 DIMSE-C 消息服务,具体参看Dicom 学习笔记-Dicom 消息服务(DIMSE-C/DIMSE-N),本文就介绍一下 DICOM 标准 ...

  2. DICOM医学图像处理:Orthanc Plugin SDK实现WADO服务

    背景: Orthanc是博主发现的一个很完美的DICOM和HTTP服务端开源软件,前几篇分别介绍了Orthanc的基本使用.Orthanc从0.8.0版本之后给出了Plugin SDK,通过该SDK可 ...

  3. DICOM医学图像处理:storescp.exe与storescu.exe源码剖析,学习C-STORE请求

    转载:http://blog.csdn.net/zssureqh/article/details/39213817 背景: 上一篇专栏博文中针对PACS终端(或设备终端,如CT设备)与RIS系统之间w ...

  4. DICOM:DICOM3.0网络通信协议(续)

    转载:http://blog.csdn.net/zssureqh/article/details/44278693 题记: 近一年来一直坚持周末写博客,整理工作和闲暇之余的点点滴滴.对于新知识点.新技 ...

  5. DICOM:DICOM3.0网络通信协议

    转载:http://blog.csdn.net/zssureqh/article/details/41016091 背景: 专栏取名为DICOM医学图像处理原因是:博主是从医学图像处理算法研究时开始接 ...

  6. DICOM:C-GET与C-MOVE对照剖析

    背景: 之前专栏中介绍最多的两款PACS各自是基于dcmtk的dcmqrscp以及Orthanc.和基于fo-dicom的DicomService(自己开发的).该类应用场景都是针对于局域网,因此在使 ...

  7. DICOM:DICOM3.0网络通信协议(延续)

    题记: 在过去的一年中一直坚持周末博客,整理工作与休闲比的点点滴滴. 新知识点.新技术的涉猎会单独成文,对于与DICOM相关的知识统一放在了DICOM医学图像处理 专栏里,事实上DICOM英文全称是D ...

  8. DICOM简介

    背景: DICOM分为两大类(这里只是从DICOM相关从业者日常工作角度出发来分类的):1)DICOM医学图像处理,即DCM文件中具体数据的处理,说图像可能有些狭隘,广义上还包括波形(心电).视频(超 ...

  9. DICOM:docker实现DICOM服务虚拟化

    背景: docker,是一个开源的应用容器引擎,眼下大多应用在部署和运维领域,然而因为全然使用沙箱机制,相互之间能够看做独立的主机,且自身对资源的需求也十分有限.远远低于虚拟机.甚至非常多时候.能够直 ...

随机推荐

  1. SVM相关知识及和softmax区别

    1.相对于容易过度拟合训练样本的人工神经网络,支持向量机对于未见过的测试样本具有更好的推广能力. 2.SVM更偏好解释数据的简单模型---二维空间中的直线,三维空间中的平面和更高维空间中的超平面. 3 ...

  2. MySQL的左外连接

    代码: select t1.descid, IFNULL(t2.cnt,) as countnew, t1.description from uosdesc t1 left outer join t2 ...

  3. 倍福TwinCAT(贝福Beckhoff)基础教程2.2 TwinCAT常见类型使用和转换_函数块

    右击POUs,添加一个FB功能块,相比于FUN,FB功能块有INPUT,OUTPUT,还有VAR,即FB可以有多个输出,但是整个FB没有返回值   实现相同的功能,FB要比FUN难看的多,FB要声明实 ...

  4. 倍福TwinCAT(贝福Beckhoff)基础教程 松下驱动器如何执行绝对值清零

    点击参数-参数一览,然后修改015为0(设置为绝对编码器方式),点击设定值变更,然后传送,EEP,将参数写入驱动器(保持USB线连接,重启驱动器并确认参数确是改成0了)   监视器-清除多圈数,清除之 ...

  5. python-mysql-replication

    python处理mysql binlog增量日志 http://python-mysql-replication.readthedocs.io/en/latest/examples.html 同样的项 ...

  6. hdu - 4974 - A simple water problem(贪心 + 反证)

    题意:N个队(N <= 100000),每一个队有个总分ai(ai <= 1000000),每场比赛比赛两方最多各可获得1分,问最少经过了多少场比赛. 题目链接:http://acm.hd ...

  7. Web用户的身份验证及WebApi权限验证流程的设计和实现(尾)

    5. WebApi 服务端代码示例 5.1 控制器基类ApiControllerBase [csharp] view plaincopy   /// /// Controller的基类,用于实现适合业 ...

  8. Mysql查询结果只有一条的情况下把值赋值给变量,再用if else 流程判断

    BEGIN set @n=(SELECT count(day) from log where day=CURDATE()); THEN call m_LogInsert(); ELSE call m_ ...

  9. 详解zabbix安装部署(Server端篇) (转)

    Linux下常用的系统监控软件有Nagios.Cacti.Zabbix.Monit等,这些开源的软件,可以帮助我们更好的管理机器,在第一时间内发现,并警告系统维护人员. 今天开始研究下Zabbix,使 ...

  10. Babel中的stage-0,stage-1,stage-2以及stage-3

    大家知道,将ES6代码编译为ES5时,我们常用到Babel这个编译工具.大家参考一些网上的文章或者官方文档,里面常会建议大家在.babelrc中输入如下代码: { "presets" ...