dicom c-echodicom服务器干什么用的

 上传我的文档
 下载
 收藏
该文档贡献者很忙,什么也没留下。
 下载此文档
正在努力加载中...
PACS即服务的云平台架构
下载积分:1500
内容提示:PACS即服务的云平台架构
文档格式:PDF|
浏览次数:11|
上传日期: 02:13:13|
文档星级:
该用户还上传了这些文档
PACS即服务的云平台架构
官方公共微信背景:上一篇博文对DICOM中的网络传输进行了介绍,主要参照DCMTK Wiki中的英文原文。通过对比DCMTK与fo-dicom两个开源库对DICOM标准的具体实现,对理解DICOM标准有一个更直观的认识。此篇博文是对上一篇博文的补充,因为专栏前面的示例大多是利用DCMTK工具包来进行的,此次借着分析fo-dicom源码结构的机会,参照fo-dicom的README.md,给出C-ECHO 和C-STORE服务的具体实现。在实现的同时给出DICOM3.0标准中的相关介绍,帮助我们理解。C-ECHO的fo-dicom实现:1)C-ECHO参数说明:C-ECHO又叫验证服务(即Verification),是用来验证DICOM服务两端的交流是否畅通。DICOM3.0的第7部分给出了C-ECHO服务的参数,如下图1所示:【注意】:这里讲解一下DICOM3.0标准的阅读方法。以DICOM3.0标准的第7、8部分为例,【第7部分】中第9章开始讲解DIMSE-C的各种服务,依次为C-STORE、C-FIND、C-GET、C-MOVE、C-ECHO(上图1就是我在该部分的C-ECHO小节中截取的),其中前半部分主要给出了DIMSE-C各种服务的参数,这里仅仅是罗列出DICOM3.0标准的要求,目的是让你明白各个服务参数是否是必要的(分别用M、U、=表示);后半部分开始讲解DIMSE-C各种服务的协议及实现流程(即Protocol和Procedures),在PROTOCOL中给出的是具体的DIMSE-C服务的各种指令在传输过程中的格式,该部分也就是你利用抓包工具能够直接抓取的真实数据流;在Procedures中给出的是SCU和SCP之间的交互流程,通常为了说明服务是由谁发起的,由谁响应。在介绍Protocol的时候对于比较复杂的、可变的区域(Variables Fields)通常会放在附录中,例如第7部分的附录C和E等;【第8部分】与【第7部分】类似,从第7章开始介绍ACSE的各种服务的参数(如下图2所示),依次为A-ASSOCIATE、A-RELEASE、A-ABORT、A-P-ABORT、P-DATA;第9章给出的是ACSE中各种服务的结构,即STRUCTURE,该部分与【第7部分】中的PROTOCOL相同,给出的是具体ACSE PDU在传输时刻的数据格式,该部分也是可以通过抓包工具直接获得的;同样对于比较复杂的STRUCTURE介绍也会单独放到附录中,例如第8部分的附录E。
fo-dicom对于DIMSE消息的实现基类是DicomMessage,针对请求和响应分别派生出了DicomRequest和DicomResponse,最后根据不同的DIMSE服务派生相应的类。C-ECHO是其中最简单的,fo-dicom已经给出了SCP和SCU的具体实现。参照fo-dicom中的README.md文件,给出C-ECHO SCP和SCU的代码,详情如下:2)C-ECHO代码实例:C-ECHO SCP的代码是直接利用了fo-dicom给出的DicomCEchoProvider类,通过创建DicomServer&DicomCEchoProvider&(12345)对象,开启C-ECHO SCP服务,其中参数12345表示C-ECHO服务的端口号。C-ECHO SCU和C-ECHO SCP的代码分别如下所示:using S
using System.Collections.G
using System.L
using System.T
using System.T
using System.Threading.T
using Dicom.N
namespace CEchoSCU
class Program
static void Main(string[] args)
var client = new DicomClient();
client.NegotiateAsyncOps();
client.AddRequest(new DicomCEchoRequest());
client.Send("127.0.0.1", 12345, false, "SCU", "ANY-SCP");
Console.ReadLine();
using System.Collections.G
using System.L
using System.T
using System.Threading.T
using System.T
using Dicom.N
namespace CEchoSCP
class Program
static void Main(string[] args)
var server = new DicomServer&DicomCEchoProvider&(12345);
Console.ReadLine();
实际运行结果如下:C-STORE的fo-dicom实现:1)C-STORE参数说明:C-STORE就是存储服务,在医疗信息系统中最常见的服务之一,尤其是PACS系统中。与C-ECHO服务相同,DICOM3.0标准第7部分也给出了C-STORE服务的参数列表,如下图4所示:
该参数列表的目的同样是为了介绍C-STORE服务中各参数的必要性,真正的参数消息格式在后续的C-STORE PROTOCOL中介绍,如下图5所示:
图5中给出的仅仅是C-STORE RQ的实际消息格式,该消息由C-STORE服务的SCU(客户端)流向C-SOTRE服务的SCP(服务端);与之相对应的C-STORE-RSP消息是从SCP流向SCU,DICOM3.0标准中也有C-STORE-RSP的详细介绍,如下图6所示。2)C-STORE代码实例:在fo-dicom的说明文档README.md中只给出了C-STORE的SCU示例,如下图7所示:
上一篇博文对fo-dicom源码结构分析的基础上可知,实现DIMSE众多服务的SCU端很容易,首先创建DicomClient实体类,代表一个客户端,然后通过AddRequest添加不同的请求即可实现各种DIMSE的客户端,如图7中C-STORE SCU的实现为:
client.AddRequest(new DicomCStoreRequest(@"test.dcm"));
DicomCStoreRequest类是DicomRequest的派生类,上述代码通过制定DCM文件路径来构建了一个DicomCStoreRequest对象,在DicomCStoreRequest内部通过打开指定的DCM文件提取获得上述参数中的Affected SOP Instance UID等参数。
既然fo-dicom中没有提供线程的C-STORE SCP实现,我们先利用DCMTK的storescp.exe工具来验证一下fo-dicom给出的C-STORE SCU的正确性,测试代码如下:SCP端利用storescp.exe,在控制台下输入:storescp.exe –d –od c:\ 12345SCU端利用fo-dicom中的C-STORE SCU,具体代码如上图7所示,然后双击生成后的storescu.exe。
最后可以得到如下结果,如图8所示:
同时在C盘根目录下可以看到被重命名的test.dcm文件,如下图9所示:
之所以被重命名我们在之前分析DCMTK开源库源码时提到过,通常DCMTK会根据SOP Instance UID(-uf,默认的)对接收到的DCM文件进行重命名,当然也可以通过选项设置重命名的方式,例如按照时间(-tn)、特定前缀(-fe)等等,如下图10所示。
由此说明fo-dicom中给出的C-STORE SCU功能正常,接下来我们尝试利用fo-dicom构建C-STORE SCP。3)构建C-STORE SCP打开C-ECHO SCP的实现DicomCEchoProvider.cs文件,我们看到DicomCEchoProvider类通过派生DicomService服务类来实现了Dicom服务的基本框架,然后通过实现IDicomServiceProvider和IDicomCEchoProvider接口,完成了C-ECHO 的服务端,仔细查看DicomCEchoProvider的代码可以发现,其实就是在接收到A-ASSOCIATE-RQ消息后,判别Presentation Context中的Abstract Syntax,根据实际请求消息来决定是否建立连接,另外当接收到C-ECHO SCU发起的C-ECHO Request时,向其会送DicomCEchoResponse确认信息即可。既然通过实现两个接口函数就可以完成C-ECHO SCP的构建,那么我们就自己尝试来完成C-STORE SCP的搭建,仿照DicomCEchoProvider的方式,DicomCStoreProvider的代码如下:using S
using System.Collections.G
using System.L
using System.T
using System.Threading.T
using Dicom.L
using Dicom.N
using System.T
using System.IO;
namespace CStoreSCP
class CStoreSCPProvider : DicomService, IDicomServiceProvider, IDicomCStoreProvider
public CStoreSCPProvider(Stream stream, Logger log) : base(stream, log) { }
public DicomCStoreResponse OnCStoreRequest(DicomCStoreRequest request)
return new DicomCStoreResponse(request,DicomStatus.Success);
public void OnCStoreRequestException(string tempFileName, Exception e)
public void OnReceiveAssociationRequest(DicomAssociation association)
foreach (var pc in association.PresentationContexts)
if (pc.AbstractSyntax == DicomUID.Verification)
pc.SetResult(DicomPresentationContextResult.Accept);
//pc.SetResult(DicomPresentationContextResult.RejectAbstractSyntaxNotSupported);
if (pc.AbstractSyntax == DicomUID.CTImageStorage)
pc.SetResult(DicomPresentationContextResult.Accept);
SendAssociationAccept(association);
public void OnReceiveAssociationReleaseRequest()
SendAssociationReleaseResponse();
public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason)
public void OnConnectionClosed(int errorCode)
然后通过var server = new DicomServer&CStoreSCPProvider&(12345);Console.ReadLine(); 来构建一个C-STORE SCP应用。
下图11是先运行CStoreSCP.exe,然后运行CStoreSCU.exe得到的结果:
从图11的输出结果可以看出,此次C-STORE SCP和SCU两端的通讯顺利完成,那么我们发送的C:\test.dcm文件会被CStoreSCP.exe存储到那里呢?由上一篇博文分析我们知道fo-dicom库中将DICOM的服务基本框架放在了DicomService类中,查看其中处理P-DATA服务的核心函数ProcessPDataTF,可以看到如下代码:var file = new DicomFile();
file.FileMetaInfo.MediaStorageSOPClassUID = pc.AbstractS
file.FileMetaInfo.MediaStorageSOPInstanceUID = _mand.Get&DicomUID&(DicomTag.AffectedSOPInstanceUID);
file.FileMetaInfo.TransferSyntax = pc.AcceptedTransferS
file.FileMetaInfo.ImplementationClassUID = Association.RemoteImplemetationClassUID;
file.FileMetaInfo.ImplementationVersionName = Association.RemoteImplementationV
file.FileMetaInfo.SourceApplicationEntityTitle = Association.CallingAE;
_dimseStream = CreateCStoreReceiveStream(file);
转到CreateCStoreReceiveStream函数内部,通过函数的说明就可以知道fo-dicom对C-STORE服务默认情况下是在系统中创建了一个临时文件,用来接收C-STORE SCU的数据,因此可以推断我们的test.dcm文件应该也在临时文件夹中,打开我本机的temp文件夹,可以看到有一个后缀为tmp的临时文件,如下图12所示。文件大小与我们测试用的test.dcm相同,尝试修改.tmp的扩展名,修改后可以使用DICOM Viewer软件正常打开,因此说明我们的C-STORE SCP顺利成功。DICOM数据流分析:C-ECHO服务数据流分析:1)工具:在本地测试,为了抓取127.0.0.1回路数据包,需要使用RawCap.exe工具包。RawCap.exe是控制台程序,在抓取本地回路数据包时很便捷。当抓取完成后我们需要借助于WireShark的强大分析功能,来实现C-ECHO数据流的详细分析,WireShark可以直接打开RawCap.exe抓取的.pcap数据包。
WireShark是功能强大的数据包统计分析工具,当然本身也可以抓取网络数据包(本地回路数据包不方便)。WireShark支持众多协议,其中包括DICOM协议。下面以C-ECHO的数据包为例,简单介绍一下如何使用WireShark来自动识别并解析DICOM数据包。首先打开抓取的本地C-ECHO数据包cecho.pcap。如图13,在Protocol中右键选择"Protocol Preferences “中的"Data Preferences…”,会弹出一个协议设置窗口如图13。在左侧列表中找到DICOM协议,勾选图14中红色部分。该部分的意思是除了检测DICOM协议默认端口104的数据包的同时也检测其他端口的数据包。之所以需要选择此项是因为很多DICOM服务并未使用协议默认的104端口。设置完成后,重新查看Protocol列,可以看到出现了DICOM字样,如图15所示,最上方的带DICOM字样的数据包就是我们抓取到的C-ECHO服务的本地回路数据包。2)C-ECHO数据流分析:利用RawCap.exe和WireShark两大强大的工具,我们已经可以直观的看到抓取的DICOM数据包了,接下来我就按照DICOM标准第7部分和第8部分中的内容,逐个数据包来分析一下,通过观察真实的数据包来加深一下对DICOM协议的理解。
从图15中可以看到,最顶部DICOM协议包含6个数据包,分别是连接建立(A-ASSOCIATE RQ/A-ASSOCIATE AC)、数据交互(P-DATA-TF)、连接释放(A-RELEASE RQ/A-RELEASE RP),这与DICOM协议第8部分中介绍的ACSE控制流程相符。A-ASSOCIATE RQ/A-ASSOCATE AC分析:双击第一个DICOM数据包,该数据包是A-ASSOCIATE RQ的真实数据流,如图16所示:
按照DICOM协议第8部分中第9章对A-ASSOCIATE RQ PDU的描述,我们来逐项对比(DICOM协议可参照图17):第一项1个字节的PDU-type,图中为01H,说明该数据包代表的是A-ASSOCIATE RQ;第二项一个字节的保留,数据流为00H;第三项是四个字节的PDU-length,图中为00 00 00 ff,转换为无符号整数正好为255,这也是整个图中蓝色部分后续的数据包长度;第四项是两个字节的Protocol-Version,图中为00 01,对应版本为1;第五项为两字节保留;第六项和第七项是我们熟悉的AE Title,从WireShark的数据流中也可以看出分别是ANY-SCP和ECHOSCU;第8项又是一堆保留字节,用00H填充;第9项是一个可变区域(Variable Fields),该项是复合项,内部包含多个独立的子项。由图16可以看出该复合项内部含有Application Context、Presentation Context(2个,ID分别是1、3)、UserInfo三个子项;而UserInfo又是一个复合项,其内部又包含了Max PDU Length、ImplentationUID、ImplentationVersion三个子项。从WireShark的分析来看,Application Context子项类型为10H、Presentation Context子项类型为20H、UserInfo子项为50H(其内部的嵌套子项的类型分别为,Max PDU Length-51H、Implentation UID-52H、Implentation Version-55H)。各个子项的类型与DICOM协议第7、8两部分中的附录D相对应,例如图19中我截取的是Max PDU Length子项的格式。A-ASSOCIATE AC的数据包分析与A-ASSOCIATE RQ类似,只是A-ASSOCIATE AC的数据流更简单一些,这里就不做详细介绍了。(最终数据域DICOM协议的对应结果如图18)。A-RELEASE RQ/A-RELEASE RP分析:连接释放的数据包格式简单,下面图20和图21分别是DICOM协议第8部分中给出的连接释放请求和应答数据包的格式:
双击WireShark中的连接释放数据包,可以看到两者的数据包类型分别为05H和06H,这与上图中DICOM协议的规定完全一致。P-DATA-TF:在上一篇博文中()我已经分析了,DICOM协议第7部分中规定的DIMSE消息(Command和Dataset)是通过第8部分中ACSE协议中的P-DATA-TF服务以PDV的形式来传输的。下面就让我们来分析一下DIMSE消息中C-ECHO RQ 和C-ECHO RSP的格式:
双击WireShark数据包中间两个,从数据流向可以断定一个是C-ECHO RQ消息,一个是C-ECHO RSP消息。先打开第一个,按照上一篇博文的分析,首先该数据包是一个P-DATA-TF PDU,因此需要符合下图23中的格式。
通过分析最外层的是代表P-DATA-TF类型的04H,然后是由DIMSE消息填充的PDV区域,该项是复合项,第一子项是Item-length,此处为46H;第二子项为Presentation-context-ID,此处为01H;第三子项又是一个复合项,是DICOM标准第4部分中给出的DIMSE消息结构,包括Message Control Header、Command和DataSet三部分。此处的MessageControlHeader为03H,即表示是Command数据而不是DataSet,且是最后一个PDV,即Last Fragment。具体的对应关系如图24所示:C-STORE服务数据流分析:1)工具:依然使用RawCap.exe+WireShark来解决。2)C-STORE数据流分析:按照C-ECHO中的分析方式,同样可以看到DICOM数据包,如图25所示:A-ASSOCIATE RQ/A-ASSOCIATE AC:对于A-ASSOCIATE RQ/A-ASSOCIATE AC的分析与C-ECHO中基本类似,唯一不同的就是对于C-STORE服务需要不同的Presentation Context描述上下文,如图26所示,此处C-STORE需要的是CT Image Storage服务,其SOP Class UID为1.2.840..4.1.1.2。A-RELEASE RQ/A-RELEASE RP:与C-ECHO中的相同,这也说明了博文中的C-ECHO 和C-STORE服务实现成功,连接能够正常释放。P-DATA-TF:此处着重分析一下C-STORE服务中的P-DATA-TF数据包,因为传输一个DCM文件需要多个PDU,自然也需要多个PDV。所以我们通过分析C-STORE的P-DATA-TF数据包可以更形象的学习Message Control Header和DIMSE的知识。
同样传输的每个数据包首先符合P-DATA-TF的格式要求,第一项是PDU类型,即04H;随后是保留项、PDU-length、PDV复合项……,这与C-ECHO中的分析相同。按照上一篇博文的分析,C-STORE PROTOCOL的流程是CSTORE SCU向SCP发送C-STORE RQ消息,但是打开图中的第一个P-DATA数据包时我们看到的却不是C-STORE RQ,而是其中的一个数据片段,如下图27所示。
依次查看后面的几个P-DATA数据包,都是类似的情况。最后倒数两个分别是C-STORE RQ中DCM文件数据的最后一个数据包(Last Fragment)和SCP向SCU发送的C-STORE RSP,具体分析如图28所示:
从最后数据包Command中的()的值域8001H可知该指令就是C-STORE RSP。
看到这里你或许会很兴奋,因为我们终于也看到了C-STORE服务的真实数据流,但是在上图中的所有DICOM对应的数据包中我们并未找到C-STORE SCU发起的C-STORE RQ数据包,那么C-STORE RQ数据包在哪里呢?
让我们将cstore.pcap的所有数据包按照时间排序,出现了大量标记为[TCP segment of a reassembled PDU]的TCP数据包。
打开第一个标记为[TCP segment of a reassembled PDU]的TCP数据包,其内部的真实数据分析如下图30所示:
至此我们顺利找到了C-STORE SCU端发送的C-STORE RQ消息,之所以没有在WireShark中以DICOM协议显示,可能是由于WireShark在识别多个连续分片的数据时不够智能。博文中的示例图和文字较多,仔细阅读后应该对DICOM3.0中的协议会有更进一步的了解。通过分析数据包的方式在更直观的学习和掌握DICOM3.0标准的同时,对后期排查DICOM网络传输相关错误也会有帮助。备注:再次说明一下阅读DICOM3.0标准的方式:
以DICOM3.0标准的第7、8部分为例,【第7部分】中第9章开始讲解DIMSE-C的各种服务,依次为C-STORE、C-FIND、C-GET、C-MOVE、C-ECHO(上图1就是我在该部分的C-ECHO小节中截取的),其中前半部分主要给出了DIMSE-C各种服务的参数,这里仅仅是罗列出DICOM3.0标准的要求,目的是让你明白各个服务参数是否是必要的(分别用M、U、=表示);后半部分开始讲解DIMSE-C各种服务的协议及实现流程(即Protocol和Procedures),在PROTOCOL中给出的是具体的DIMSE-C服务的各种指令在传输过程中的格式,该部分也就是你利用抓包工具能够直接抓取的真实数据流;在Procedures中给出的是SCU和SCP之间的交互流程,通常为了说明服务是由谁发起的,由谁响应。在介绍Protocol的时候对于比较复杂的、可变的区域(Variables Fields)通常会放在附录中,例如第7部分的附录C和E等;【第8部分】与【第7部分】类似,从第7章开始介绍ACSE的各种服务的参数(如图2所示),依次为A-ASSOCIATE、A-RELEASE、A-ABORT、A-P-ABORT、P-DATA;第9章给出的是ACSE中各种服务的结构,即STRUCTURE,该部分与【第7部分】中的PROTOCOL相同,给出的是具体ACSE PDU在传输时刻的数据格式,该部分也是可以通过抓包工具直接获得的;同样对于比较复杂的STRUCTURE介绍也会单独放到附录中,例如第8部分的附录E。实例工程及抓取的数据包:代码:数据包:后续专栏博文介绍:利用PHP Skel结合DCMTK开发WEB PACS应用利用oracle直接操作DICOM数据C#的异步编程模式在fo-dicom中的应用VMWare三种网络连接模式的实际测试作者:时间:
如果您想留下此文,您可以将其发送至您的邮箱(将同时以邮件内容&PDF形式发送)
相关文章推荐
(Ctrl+Enter提交) &&
已有0人在此发表见解
&在& 19:00收藏到了
&&在信息爆炸的时代,您的知识需要整理,沉淀,积累!Lai18为您提供一个简单实用的文章整理收藏工具,在这里您可以收藏对您有用的技术文章,自由分门别类,在整理的过程中,用心梳理自己的知识!相信,用不了多久,您收藏整理的文章将是您一生的知识宝库!
· 蜀ICP备号-1&&&&DICOM 常用服务C-STORE C-ECHO等代码实例
&DICOM 常用服务C-STORE C-ECHO等代码实例
DICOM 常用服务C-STORE C-ECHO等代码实例
若举报审核通过,可奖励20下载分
被举报人:
zhangqipeng3092
举报的资源分:
请选择类型
资源无法下载
资源无法使用
标题与实际内容不符
含有危害国家安全内容
含有反动色情等内容
含广告内容
版权问题,侵犯个人或公司的版权
*详细原因:
VIP下载&&免积分60元/年(1200次)
您可能还需要
Q.为什么我点的下载下不了,但积分却被扣了
A. 由于下载人数众多,下载服务器做了并发的限制。若发现下载不了,请稍后再试,多次下载是不会重复扣分的。
Q.我的积分不多了,如何获取积分?
A. 获得积分,详细见。
完成任务获取积分。
论坛可用分兑换下载积分。
第一次绑定手机,将获得5个C币,C币可。
关注并绑定CSDNID,送10个下载分
下载资源意味着您已经同意遵守以下协议
资源的所有权益归上传用户所有
未经权益所有人同意,不得将资源中的内容挪作商业或盈利用途
CSDN下载频道仅提供交流平台,并不能对任何下载资源负责
下载资源中如有侵权或不适当内容,
本站不保证本站提供的资源的准确性,安全性和完整性,同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
开发技术下载排行
您当前C币:0&&&可兑换 0 下载积分
兑换下载分:&
消耗C币:0&
立即兑换&&
兑换成功你当前的下载分为 。前去下载资源
你下载资源过于频繁,请输入验证码
如何快速获得积分?
你已经下载过该资源,再次下载不需要扣除积分
DICOM 常用服务C-STORE C-ECHO等代码实例
所需积分:0
剩余积分:0
扫描微信二维码精彩活动、课程更新抢先知
VIP会员,免积分下载
会员到期时间:日
剩余下载次数:1000
DICOM 常用服务C-STORE C-ECHO等代码实例
剩余次数:&&&&有效期截止到:
你还不是VIP会员VIP会员享免积分 . 专属通道极速下载
VIP下载次数已满VIP会员享免积分 . 专属通道极速下载,请继续开通VIP会员
你的VIP会员已过期VIP会员享免积分 . 专属通道极速下载,请继续开通VIP会员接下来可以进行消息传递了 ,也就是dimse ,再来复习下 什么是dimse 。n-set& n-create c-echo 这些都是dimse& 他们都是属于一种结构的pdu 那就是tf-pdu(传输数据和命令的都称之为tf-pdu 或者transfer pdu&,协商连接的都称之为associcate pdu) 。dimse 由 许多tag组成,就像文件解析那篇博文一样。tf-pdu数据结构分析如下:
如果你又要问此图是怎么来的 ,dicom标准第八章 33页。你可能又要问 dimse又是什么 ,dimse全称 dicom 消息服务元素DIMSE(DICOM Message Service Element)为什么你就说echo属于dimse 。请看dicom标准第七章 的目录: 9&&&& DIMSE-C&&&&&&& 9.1.5&&&&& C-ECHO SERVICE 。不用我多说了噻。在这之前还是像以前一样先把tf-pdu的数据结构跟序列化搞了吧:
struct PDVset
public byte pduT
public uint pduL
//pdv长度从作用来看他跟上面有所重复 pdv, presentation data value
public uint itemL
//这个contextID其实是指协商连接时的presentation context id
//最好判断下是否跟协商时的一致
public byte contextID;
//消息控制头 确定pdv类型是command 还是data 发送完成与否
public byte msgControlH
public SortedDictionary&uint, DataElement&
public byte[] serial()
if ((pduLen != 0 && itemLen != 0) == false)
return null;
MemoryStream _stream = new MemoryStream((int)pduLen + 6);
WarpedStream stream = new WarpedStream(_stream);
stream.writeByte(0x04);
stream.skip_write(1);
stream.writeUint(pduLen);
stream.writeUint(itemLen);
stream.writeByte(contextID);
stream.writeByte(msgControlHeader);
foreach (DataElement item in elements.Values)
stream.writeBytes(item.serial());
_stream.Flush();
byte[] data = new byte[_stream.Length];
Array.Copy(_stream.GetBuffer(), data, _stream.Length);
stream.close();
_stream.Close();
enum pdvType
command, data, commandAndData
struct DataElement
public uint _
public WarpedStream.byteOrder bytO
public bool explicitVR;//是显式VR的 否则隐式VR
public uint tag
get { return _ }
VR = VRs.GetVR(value);
uint _len = VRs.getLen(VR);
if (_len != 0)
public ushort VR;
//虽然长度为uint 但要看情况隐式时都是4字节 显式时除ow那几个外都是2字节
//如果为ow 显示不但长度为4 在之前还要跳过2字节,除ow那几个之外不用跳过
public uint
public byte[]
public IList&DataElement&//子项
public bool haveI//是否包含子项
//值的显示
public string showValue()
if (haveItems )
return null;
if (value != null)
return Tags.VFdecoding(VR, value, bytOrder);
return null;
public void setValue(string valStr)
if (haveItems )
if (VRs.IsStringValue(VR)) {
len = (uint)valStr.L
value = Tags.VFencoding(VR, valStr, bytOrder, len);
else if (len != 0)//就是这个破地方 因为element的连续使用 导致会截断字符串
value = Tags.VFencoding(VR, valStr, bytOrder, len);
value = Tags.VFencoding(VR, valStr, bytOrder);
if (VRs.IsStringValue(VR))
len = (uint)value.L
public byte[] serial()
MemoryStream data_serial = new MemoryStream();
serial(this, data_serial);
byte[] data = new byte[data_serial.Length];
Array.Copy(data_serial.GetBuffer(), data, data.Length);
data_serial.Close();
//序列化的递归调用
public void serial(DataElement element, MemoryStream data_serial)
//int len_serial = element.getSerialLen();
//if ((VR == VRs.SQ && len_serial == UInt32.MaxValue) || (tag == 0xfffee000 && len == UInt32.MaxValue))//靠 遇到文件夹开始标签了
if (element.haveItems )
//开始标记
data_serial.WriteByte((byte)((element._tag & 0x00ff0000) && 16));
data_serial.WriteByte((byte)((element._tag & 0xff000000) && 24));
data_serial.WriteByte((byte)(element._tag & 0x000000ff));
data_serial.WriteByte((byte)((element._tag & 0x0000ff00) && 8));
data_serial.WriteByte(0xff); data_serial.WriteByte(0xff); data_serial.WriteByte(0xff); data_serial.WriteByte(0xff);
foreach (DataElement item in element.items)
item.serial(item, data_serial);
//结束标记
if (element.VR == VRs.SQ)
data_serial.WriteByte((byte)((0xfffee0dd & 0x00ff0000) && 16));
data_serial.WriteByte((byte)((0xfffee0dd & 0xff000000) && 24));
data_serial.WriteByte((byte)(0xfffee0dd & 0x000000ff));
data_serial.WriteByte((byte)((0xfffee0dd & 0x0000ff00) && 8));
data_serial.WriteByte((byte)((0xfffee00d & 0x00ff0000) && 16));
data_serial.WriteByte((byte)((0xfffee00d & 0xff000000) && 24));
data_serial.WriteByte((byte)(0xfffee00d & 0x000000ff));
data_serial.WriteByte((byte)((0xfffee00d & 0x0000ff00) && 8));
data_serial.WriteByte(0x00); data_serial.WriteByte(0x00); data_serial.WriteByte(0x00); data_serial.WriteByte(0x00);
byte[] data = new byte[element.getSerialLen()];
uint _len = element.
if (_len % 2 != 0)
if (element.VR == VRs.SQ)
_len = 0xffffffff;
data[0] = (byte)((element._tag & 0x00ff0000) && 16);
data[1] = (byte)((element._tag & 0xff000000) && 24);
data[2] = (byte)(element._tag & 0x000000ff);
data[3] = (byte)((element._tag & 0x0000ff00) && 8);
if (element.explicitVR)//显示VR
data[4] = 0x00;
data[5] = 0x00;
if (VRs.IsLengthField16Bit(VR))
if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
data[6] = (byte)(_len & 0x000000ff);
data[7] = (byte)((_len & 0x0000ff00) && 8);
data[6] = (byte)((_len & 0x0000ff00) && 8);
data[7] = (byte)(_len & 0x000000ff);
for (int i = 0; i & element.value.L i++)
data[8 + i] = element.value[i];
if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
data[6] = (byte)((_len & 0xff000000) && 24);
data[7] = (byte)((_len & 0x00ff0000) && 16);
data[8] = (byte)((_len & 0x0000ff00) && 8);
data[9] = (byte)(_len & 0x000000ff);
data[6] = (byte)(_len & 0x000000ff);
data[7] = (byte)((_len & 0x0000ff00) && 8);
data[8] = (byte)((_len & 0x00ff0000) && 16);
data[9] = (byte)((_len & 0xff000000) && 24);
if (element.value == null)
throw new Exception(string.Format("异常:tag{0} value未赋值 ", Tags.ToHexString(element.tag)));
for (int i = 0; i & element.value.L i++)
data[10 + i] = element.value[i];
//len_ser = (int)(4 + 2 + 4 + len);
//len_ser = (int)(4 + 2 + len);
else //隐式Vr
if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
data[4] = (byte)((_len & 0xff000000) && 24);
data[5] = (byte)((_len & 0x00ff0000) && 16);
data[6] = (byte)((_len & 0x0000ff00) && 8);
data[7] = (byte)(_len & 0x000000ff);
data[4] = (byte)(_len & 0x000000ff);
data[5] = (byte)((_len & 0x0000ff00) && 8);
data[6] = (byte)((_len & 0x00ff0000) && 16);
data[7] = (byte)((_len & 0xff000000) && 24);
if (element.value == null)
throw new Exception(string.Format("异常:tag{0} value未赋值 ", Tags.ToHexString(element.tag)));
for (int i = 0; i & element.value.L i++)
data[8 + i] = element.value[i];
data_serial.Write(data, 0, data.Length);
//获取单个元素序列化长度的递归调用
public void getSerialLen_item(DataElement element, ref int len)
if (element.haveItems)
len += element.getHeaderLen();
foreach (DataElement item in element.items)
getSerialLen_item(item, ref len);
if (element.value != null)
len += element.getHeaderLen() + element.value.L
len += element.getHeaderLen();
if (len % 2 != 0)//文件元信息元素整体字节数一定是偶数(包括tag VR 数据长度 数据 这些一起)
//获取序列化后整个元素的长度
public int getSerialLen()
int serial_len=0;
getSerialLen_item(this, ref serial_len);
return serial_
//获取item的header长度
public int getHeaderLen()
int len_ser = 0;
int len_tmp = 0;
if (explicitVR)//显示VR
if (tag == 0xfffee000 || tag == 0xfffee00d || tag == 0xfffee0dd)
len_ser = 4 + 4;
else if (VR == VRs.OB || VR == VRs.OW || VR == VRs.OF ||
VR == VRs.UT || VR == VRs.SQ || VR == VRs.UN)
len_ser = (int)(4 + 2 + 4 + len_tmp);
len_ser = (int)(4 + 2 + len_tmp);
else //隐式Vr
len_ser = (int)(4 + 4 + len_tmp);
return len_
不要问我pdv是啥 第一篇就出现过,pdv 即p data value ,它包括许多的data element 也就是俗称tag。一个元素接一个元素 直到结束 跟文件解析的时候一样 ,他的vr方式 以及字节序 在协商连接的时候就已确定 你只管读就是了。那么新的问题又来了 echo这个dimse到底包含哪些tag 他们的值又应该各是多少?为了解决你这个疑问我又要翻一个表出来:你又要问这个表是怎么来的 ,dicom第七章 53页。具体每个tag的作用各是什么 请参照右边的说明,有三个地方我要提一下:
affected sop class uid 受影响的sop uid ,看过第一篇里图的筒子都知道 打印有打印sop uid ,filmbox 有filmboxuid,那么这里echo也有 对应的 他就是 :
1 //SOPClass: Verification SOP Class
2 public const String Verification = "1.2.840.";
为啥是这个 我不会再说了 你懂的。command field 这个是作甚的,他的作用是用来区分不同的dimse 比如 c-create& c-find ,不用我多讲了 旁边的说明已经很明显了 echo时他的值应设置成0x0030& 。command data set type数据集选项 ,说白了就是给个标识 后面有无数据集,我们这里自然是没有 那么应设置成0x0101 。
好了开工吧,打住 不是说还有服务类规范么 ,只有复杂的才有服务类规范 我们这个echo是非常非常非常之简单的 所以没有服务类规范 直接开动吧:
//组织Verification_CECHORSP响应原语
//rq端无data ,rsp端无data
public void Verification_CECHORQ()
PDVset rq = new PDVset();
rq.pduType = 0x04;
rq.contextID = pstContextId;
rq.msgControlHeader = 0x03;
rq.elements = new SortedDictionary&uint, DataElement&();
int len = 0;
DataElement element = new DataElement();
element.bytOrder = bytO
element.tag = 0x;
element.setValue(UIDs.Verification);
rq.elements.Add(0x, element);
len += (element.getSerialLen());
element.tag = 0x;
element.setValue(0x0030.ToString());
rq.elements.Add(0x, element);
len += (element.getSerialLen());
element.tag = 0x;
element.setValue(0x03.ToString());
rq.elements.Add(0x, element);
len += (element.getSerialLen());
element.tag = 0x;//有无对应的数据段
element.setValue(0x0101.ToString());
rq.elements.Add(0x, element);
len += (element.getSerialLen());
element.tag = 0x;//消息原语数据长度
element.setValue(len.ToString());
rq.elements.Add(0x, element);
//len += (element.getSerialLen());
rq.itemLen = (uint)(12 + 2 + len);
rq.pduLen = rq.itemLen + 4;
//进行c-echo-rsp响应
stream.writeBytes(rq.serial());
看看代码里面特定的地方 是不是跟我上面描述的一样?就这样so easy& 。看到没 其实我这些都是在dicom文档里翻的 就这样而已没什么神奇的 相信你也能。再来复习下dicom标准跟网络通讯相关的几个章节 :
DICOM Part 4: Service Class Specifications& ,服务类规范
DICOM Part 7: Message Exchange&消息交换
DICOM Part 8: Network Communication Support for Message Exchange&网络通讯对消息交换的支持
按照他们的套路来 就水到渠成 。
这是我的测试结果 不用怀疑哥的水平 哥是拿到医院去测试过的:
阅读(...) 评论()}

我要回帖

更多关于 dicom胶片打印服务器 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信