dicom网络通讯入门(2)

转自:http://www.cnblogs.com/assassinx/p/3649498.html

第二篇,前面都是闲扯 其实正文现在才开始,这次是把压箱底的东西都拿出来了。 首先我们今天要干的事是实现一个echo响应测试工具 也就是echo 的scu,不是实现打印作业管理么。同学我告诉你还早着呢。本来标题取的就是《dicomviewer 第二弹 之 实现打印管理》名字多霸气,最后我又改回来了。
 
首先你得把数据组织方式搞懂 那就是pdu  和dimse  元素  数据元素。然后基于这之上你得把协商连接这块搞懂 ,协商连接都没通过不用说后面的了。然后你得把实现一个功能 比如打印 ,scu跟scp之间你来我往的 过程和概念搞懂 也就是dimse 然后才是服务类。最够你全都理解了 并且写出东西来了能跟医院的设备正常 连接和操作了,那么恭喜你 差不多了。

最后要说的是: 解析dicom文件那篇你们都已经看过了,dicom网络通讯跟解析文件是一样的 只不过解析的是socket数据流里的 元素 数据结构本身是一样的,然后他有一些规范和标准 ,这就是dimse 和服务类 这些好像都在dicom标准的第四章  第八章 第七章 。

实现这一大坨的东西 有点望而却步了吧,其实总结起来就一句话 概括 按照dicom标准 封装数据 处理数据 ,然后根据特殊的参数和应用场景 依规范响应数据,。
 
好废话少说,开工 看过 标准简介那篇博客 的都知道:
PDU是一种数据结构 dataElement是一种数据结构
pdu结构总共7种 其中用于连接控制的就占了6种 
A-Associate—RQ PDU
连接请求协议数据单元,用于关联请求。
A-Associate.AC PDU
基于DICOM标准的医学图像通信过程的实现
连接接受协议数据单元,
A.Associate—RJ PDU
连接拒绝协议数据单元,
A-Release-RQ PDU
用于对关联请求的应答。
用于拒绝关联请求。
连接释放请求协议数据单元,
A-Release.RSP PDU
连接释放响应协议数据单元,
A.Abort PDU
传输内容的pdu只有一种P.DATA.TF PDU,
当通讯双方建立了关联之后,就可以使用P.DATA-TF所提供的传输服务来实现不同的通信功能了。
 总之你在进行后面的dimse发送之前先得建立连接,否则你什么也搞不了。
好下面就协商连接的pdu进行分析:

你问这图是怎么来的 dicom 第八章 31页。是不是跟上面说的是一样的 开始两字节 然后4字节表示长度,只不过这个更详细了。协商连接pdu说起有6种 其实有好多是大同小异 比如Associate pdu, 他分rq 和ac  rq是请求 ac是响应。
我把协商连接概述一下

概述之前,什么是通讯:
还是炒剩饭 我又得把以前说过的话像背书一样的背一遍了 其实他确实是那么回事。什么是通讯 :
命令tag +数据tag  一起组成sop ,就好象说一句这样的话:“把这根萝卜拿去给我切了”    “喏  ,萝卜” 。其实这就是通讯  跟人与人之间传达意思一样。说话的时候太熟练了  没察觉到  你要仔细去想  你自己是一台电脑   ,会是一个什么样的步骤。
网络传输 跟文件组织 是一样的格式 。不过有命令tag 。很多命令tag组合到一堆这称之为dimse。 echo n-create c-find c-store 这些都称之为dimse ,记住没有c-print  大哥
打印管理是由很多组dimse 包括n-create 那些 你来我往的一套组成 比如 先n-create 什么东西 再 n-set什么东西,他有一种逻辑规范 什么参数错误则不进行n-set。这很多组dimse称之为服务类 ,比如打印管理 就是一个服务类。这些规范在dicom标准的第八章有说明。总之在dimse传递之前 你必须得协商连接。
dicom标准的地址是这个http://medical.nema.org/standard.html,英文的 。看也比较困难 装装面子,主要是理解就行了 我看的也是别人翻译的中文的。不过官方的就是官方的 没办法 某些地方你找不到原因 想参照最标准的指示 你还是得硬着头皮去看英文文档。
  
Associate pdu 协商连接的过程:
又说多了 不论如何在进行dimse之前必须得进行连接协商 因为你与别人进行通讯首先你得确定几个东西。 ,谈话的主题是什么,你是用哪国语言。这两个东西一个称之为虚拟语法 abssyntax  一个称之为传输语法 transfersyntax 传输语法其实主要确定两个东西 字节序 和 vr表示方式 ,如果你不知道字节序是什么 请自己百度 vr表示方式 跟文件解析一样的,他们两个一起被称之为表达上下文。注意表达上下文有多个 每个都有id。如果你是scp端 那么连接协商响应 也就是association-ac的时候你要告知 以你scp程序的服务能力可以完成哪些表达上下文的服务 传输语法语法是什么,如果服务不了也要给出对应的上下文id 并进行告知。这样的话scu端知道你服务不了就知难而退 主动断开连接。 其次还有些其他东西比如pdu最大数据长度 一般是0x4000。好了讲完了 这就是协商连接的过程 对照上面的图理解了否。
这是官方的解释:

官方的解释 网络协议是分层的,Dicom ul p ,称之为dicom上层协议。  也就是上图的dicom ul service provider。 反正要按照osi的标准来, 也就是说要定义一个associate-rq 或者 ac的数据结构来,一切的数据序列化或者反序列化都由 dicom ul service provider 来进行,反正只怕忽悠不死你。反正他说是那样说 我们自己按照自己的方式来。
 
好终于要动代码了 ,我喜欢的事情来了 噢啦啦啦。其实这是一个抽象化的过程,把你的想法付诸行动 代码化.就像某人说过的 主要的不是技术 而是思路。分成两步 根据文档定义 associate Pdu的数据结构,遵循上面说的原则 一个associate pdu有多高 pst Item,我们把pst Item定义为子项,然后serial()是associate pdu的网络序列化函数:

  1 public enum PDUTypes
  2     {
  3         AssociateRQ = 0x01,
  4         AssociateAC = 0x02,
  5         AssociateRJ = 0x03,
  6         DataTransfer = 0x04,
  7         AssociateReleaseRQ = 0x05,
  8         AssociateReleaseRP = 0x06,
  9         AssociateAbort = 0x07,
 10
 11         ApplicationContext = 0x10,
 12         PresentationContext = 0x20,
 13         UserInformation = 0x50,
 14     }
 15
 16     struct PDUAssociate {
 17         //header
 18         public byte pduType;
 19         public uint length;
 20         public ushort ProteocalVersion;
 21         public string CallEdAE ;//length=16
 22         public string CallingAE ;
 23
 24         //10
 25         public byte appType;
 26         public ushort appLength;
 27         public string appName;
 28
 29         //20
 30         public IList<PstItem> pstItems;
 31         //50 userinfo
 32         public byte userinfoType;
 33         public ushort userinfoLength;
 34         public byte maxnumType;
 35         public ushort maxnumLength;
 36         public uint maxnum;//DATA-TF PDU的可变字段的最大长度 一般为0x400 即1024
 37         public byte impType;//关于实现类的
 38         public ushort impLength;
 39         public string impUID;
 40         public byte impVersionType;
 41         public ushort impVersionLength;
 42         public string impVersion;
 43
 44         public byte[] serial()
 45         {
 46             if (length == 0)
 47                 return null;
 48             MemoryStream _stream = new MemoryStream((int)length + 6);
 49             WarpedStream stream = new WarpedStream(_stream);
 50             #region 序列化aassociateAC PDU
 51             //header
 52             stream.writeByte(pduType);
 53             stream.skip_write(1);
 54             stream.writeUint(length);//最低要94 我去 这是为什么呢
 55             stream.writeUshort(ProteocalVersion);
 56             stream.skip_write(2);
 57             stream.writeString(CallEdAE, 16);
 58             stream.writeString(CallingAE, 16);
 59             stream.skip_write(32);
 60
 61             //10
 62             stream.writeByte(appType);
 63             stream.skip_write(1);
 64             stream.writeUshort(appLength);
 65             stream.writeString(appName, 0);
 66             //21
 67
 68             for (int i = 0; i < pstItems.Count; i++)
 69             {
 70                 if (pstItems[i].used)
 71                 {
 72                     stream.writeByte(pstItems[i].pstType);
 73                     stream.skip_write(1);
 74                     stream.writeUshort(pstItems[i].pstLength);
 75                     stream.writeByte(pstItems[i].pstID);
 76                     stream.skip_write(3);
 77                     //if (pstItems[i].used)
 78                         //stream.writeBytes(new byte[] { 0x00, 0x00, 0x00 });
 79                     //else
 80                     //30
 81                     if (pstItems[i].pstType == 0x20)//如果是20则为printSCU
 82                     {
 83                         stream.writeByte(pstItems[i].absType);
 84                         stream.skip_write(1);
 85                         stream.writeUshort(pstItems[i].absLength);
 86                         stream.writeString(pstItems[i].absStr, 0);
 87                     }
 88
 89                     stream.writeByte(pstItems[i].tsfType);
 90                     stream.skip_write(1);
 91                     if (pstItems[i].used)
 92                         stream.writeUshort(pstItems[i].tsfLeghth);
 93                     else
 94                         stream.writeUshort(0);
 95                     if (pstItems[i].used)
 96                         stream.writeString(pstItems[i].tsfStr, 0);
 97                 }
 98                 else
 99                 {
100                     stream.writeByte(pstItems[i].pstType);
101                     stream.skip_write(1);
102                     stream.writeUshort(0x08);
103                     stream.writeByte(pstItems[i].pstID);
104                     stream.writeBytes(new byte[] { 0x00, 0x04, 0x00 });
105                     stream.writeBytes(new byte[] { 0x40, 0x00, 0x00, 0x00 });
106                 }
107             }
108
109
110             //50
111             stream.writeByte(userinfoType);
112             stream.skip_write(1);
113             stream.writeUshort(userinfoLength);
114
115             stream.writeByte(maxnumType);
116             stream.skip_write(1);
117             stream.writeUshort(maxnumLength);
118             stream.writeUint(maxnum);
119
120             stream.writeByte(impType);
121             stream.skip_write(1);
122             stream.writeUshort(impLength);
123             stream.writeString(impUID, 0);
124
125             stream.writeByte(impVersionType);
126             stream.skip_write(1);
127             stream.writeUshort(impVersionLength);
128             stream.writeString(impVersion, 0);
129             #endregion
130
131             _stream.Flush();
132             byte[] data = _stream.GetBuffer();
133             stream.close();
134             _stream.Close();
135             return data;
136         }
137     }
138
139     struct PstItem
140     {
141         //20 abstractsyntax transfersyntax传输语法
142         public byte pstType;
143         public ushort pstLength;
144         public byte pstID;
145         public bool used;
146         //public byte pstRec; //保留字节 有效项是00 00 00 无效项是00 04 00
147         public byte absType;//20的子项 30 40 读取的时候应该跟20一并读出来
148         public ushort absLength;
149         public string absStr;
150         public byte tsfType; //传输语法项 本来也有多个 为了方便只写一个
151         public ushort tsfLeghth;
152         public string tsfStr;
153     }

构建一个associate-rq的pdu 并发送:

 1 public bool associateRQ()//请求建立连接
 2         {
 3             PDUAssociate pdu_ac = new PDUAssociate();
 4             #region 构造associateAC PDU
 5             //10
 6             pdu_ac.appType = 0x10;
 7             pdu_ac.appLength = (ushort)UIDs.DICOMApplicationContextName.Length;//pdu_associate_rq.appLength
 8             pdu_ac.appName = UIDs.DICOMApplicationContextName;//pdu_associate_rq.appName
 9
10             //20
11             //30 abs
12             //40 transfer syntax
13             pdu_ac.pstItems = new List<PstItem>();
14
15             PstItem pst_ac = new PstItem();
16
17             pst_ac.absType = 0x30;
18             pst_ac.absLength = (ushort)UIDs.Verification.Length;
19             pst_ac.absStr = UIDs.Verification;
20
21             pst_ac.tsfType = 0x40;
22             pst_ac.tsfLeghth = (ushort)UIDs.ImplicitVRLittleEndian.Length;//pdu_associate_rq.pstItems[i].tsfLeghth;
23             pst_ac.tsfStr = UIDs.ImplicitVRLittleEndian;//pdu_associate_rq.pstItems[i].tsfStr;
24
25             pst_ac.pstType = 0x20;
26             pst_ac.pstLength = (ushort)(4 + (4 + pst_ac.tsfLeghth) + (4 + pst_ac.absLength));
27             pst_ac.pstID = 0x01;//表达上下文ID,多个表达上下文的时候以作区分。这里我们为发送方 主动控制为01
28             pst_ac.used = true;
29             pdu_ac.pstItems.Add(pst_ac);
30
31             //50
32             pdu_ac.userinfoType = 0x50;
33             pdu_ac.maxnumType = 0x51;
34             pdu_ac.maxnumLength = 0x04;
35             pdu_ac.maxnum = 0X4000;//16384
36
37             pdu_ac.impType = 0x52;
38             pdu_ac.impLength = (ushort)UIDs.ImplementionUid.Length;
39             pdu_ac.impUID = UIDs.ImplementionUid;
40
41             pdu_ac.impVersionType = 0x55;
42             pdu_ac.impVersionLength = 11;
43             pdu_ac.impVersion = "ASSASSMedic";
44
45             pdu_ac.userinfoLength = (ushort)(4 * 3 + pdu_ac.maxnumLength + pdu_ac.impVersionLength + pdu_ac.impLength);
46
47             //header
48             pdu_ac.pduType = 0x01;
49             pdu_ac.ProteocalVersion = 0x01;
50             pdu_ac.CallEdAE = calledAET;
51             pdu_ac.CallingAE = callingAET;
52             pdu_ac.length = (uint)((74 - 6) + (pdu_ac.appLength + 4) +
53                 (pdu_ac.userinfoLength + 4));
54
55             for (int i = 0; i < pdu_ac.pstItems.Count; i++)
56             {
57                 if (pdu_ac.pstItems[i].used)
58                     pdu_ac.length += (ushort)(pdu_ac.pstItems[i].pstLength + 4);
59                 else
60                     pdu_ac.length += 12;
61             }
62
63             #endregion
64             //序列化
65             stream.writeBytes(pdu_ac.serial());
66
67             Console.WriteLine(string.Format("associate create success,CalledAET:{0}", calledAET));
68
69             return false;
70         }

在这之前你还是得连接到SCP端:

 1 public void run(string ipStr, int port)
 2         {
 3             TcpClient _client = new TcpClient();
 4             IPAddress ipdrs = IPAddress.Parse(ipStr);
 5             _client.Connect(ipdrs, port);
 6
 7             if (_client.Connected == false)
 8             {
 9                 Console.WriteLine("与所指定主机连接失败");
10                 Console.WriteLine("连接断开");
11                 return;
12             }
13             WarpedStream stream = new WarpedStream(_client.GetStream());
14
15             echo(stream);
16             stream.close();
17             _client.Close();
18         }
19
20         public void echo(WarpedStream _stream)
21         {
22             stream = _stream;
23             //第一步协商连接
24             associateRQ();
25             PDUTypes PduType = (PDUTypes)stream.readByte();
26             stream.skip(1);
27             uint pduLen = stream.readUint();
28             stream.skip((int)pduLen);
29             Console.WriteLine("协商连接成功");
30             //第二步 进行echo 请求
31             Verification_CECHORQ();
32             PduType = (PDUTypes)stream.readByte();
33             stream.skip(1);
34             pduLen = stream.readUint();
35             stream.skip((int)pduLen);
36             release();
37             Console.WriteLine("echo测试成功");
38         }

注意我们并没有对收到的associate-ac数据进行解码验证,直接偷懒略过了 。我们默认对方都是好人 都是按照套路来的 并且能够承担我们所请求的echo服务。

时间: 2024-07-30 07:10:28

dicom网络通讯入门(2)的相关文章

dicom网络通讯入门(3)

转自:http://www.cnblogs.com/assassinx/p/3649637.html 接下来可以进行消息传递了 ,也就是dimse ,再来复习下 什么是dimse .n-set  n-create c-echo 这些都是dimse  他们都是属于一种结构的pdu 那就是tf-pdu(传输数据和命令的都称之为tf-pdu 或者transfer pdu ,协商连接的都称之为associcate pdu) .dimse 由 许多tag组成,就像文件解析那篇博文一样.tf-pdu数据结构

dicom网络通讯入门(1)

转自:http://www.cnblogs.com/assassinx/p/3649103.html 看标准 越看越糊,根本原因:dicom抽象得非常严重,是“专家”弄的.没办法. 又是什么服务类 又是什么sop,相信你把dicom标准看到头大 都不知如何下手. 不就是 socket么 这有何难. 首先你得理解神马叫pdu,从pdu入门 ,我只能这么说了.pdu就是pdu  protocol data unit   反正就是这么个概念  你把它理解为socket数据包就行了.他的结构是开始1字节

java网络编程入门教程

网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编程是比较复杂的系统工程,需要了解很多和网络相关的基础知识,其实这些都不是很必需的.首先来问一个问题:你 会打手机吗?很多人可能说肯定会啊,不就是按按电话号码,拨打电话嘛,很简单的事情啊!其实初学者如果入门网络编程的话也可以做到这么简单! 网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据.

[转帖]脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?

脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么? http://www.52im.net/thread-1732-1-1.html 1.引言 本文接上篇<脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手>,继续脑残式的网络编程知识学习 ^_^. 套接字socket是大多数程序员都非常熟悉的概念,它是计算机网络编程的基础,TCP/UDP收发消息都靠它.我们熟悉的web服务器底层依赖它,我们用到的MySQL关系数据库.Redis内存数据库底层依赖它.我们用微信和别

[转帖]脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手 http://www.52im.net/thread-1729-1-1.html 1.引言 网络编程中TCP协议的三次握手和四次挥手的问题,在面试中是最为常见的知识点之一.很多读者都知道"三次"和"四次",但是如果问深入一点,他们往往都无法作出准确回答. 本篇文章尝试使用动画图片的方式,来对这个知识点进行"脑残式"讲解(哈哈),期望读者们可以更加简单.直观地理解TCP网络通信交互的本

Android网络通讯简介

网络通信应该包含三部分的内容:发送方.接收方.协议栈.发送方和接收方是参与通信的主体,协议栈是发送方和接收方进行通信的契约.按照服务类型,网络通信可分为面向连接和无连接的方式.面向连接是在通信前建立通信链路,而通信结束后释放该链路.无连接的方式则不需要在通信前建立通信连接,这种方式不保证传输的质量. Android提供了多种网络通信的方式,如Java中提供的网络编程,在Android中都提供了支持.Android中常用的网络编程方式如下: 针对TCP/IP协议的Socket和ServerSock

[转] C#.Net Socket网络通讯编程总结

1.理解socket1).Socket接口是TCP/IP网络的应用程序接口(API).Socket接口定义了许多函数和例程,程序员可以用它们来开发TCP/IP网络应用程序.Socket可以看成是网络通信上的一个端点,也就是说,网络通信包括两台主机或两个进程,通过网络传递它们之间的数据.为了进行网络通信,程序在网络对话的每一端都需要一个Socket. 2).TCP/IP传输层使用协议端口将数据传送给一台主机的特定应用程序,从网络的观点看,协议端口是一个应用程序的进程地址.当传输层模块的网络软件模块

《连载 | 物联网框架ServerSuperIO教程》-4.如开发一套设备驱动,同时支持串口和网络通讯。附:将来支持Windows 10 IOT

感谢唯笑志在分享 原博主原地址:http://www.cnblogs.com/lsjwq/ 注:ServerSuperIO有可能被移植到Windows 10 IOT上,那么将来有可能开发一套设备驱动,可以支行在服务端.嵌入式设备中,将形成完整的解决方案.       现在已经调试通过部分代码,还得需要一段时间,一般都是晚上干,时间也有限.如下图: 目       录 4.如开发一套设备驱动,同时支持串口和网络通讯... 2 4.1           概述... 2 4.2          

网络--三种网络通讯方式及Android的网络通讯机制

Android平台有三种网络接口可以使用,他们分别是:java.net.*(标准Java接口).Org.apache接口和Android.net.*(Android网络接口).下面分别介绍这些接口的功能和作用. 1.标准Java接口 java.net.*提供与联网有关的类,包括流.数据包套接字(socket).Internet协议.常见Http处理等.比如:创建URL,以及URLConnection/HttpURLConnection对象.设置链接参数.链接到服务器.向服务器写数据.从服务器读取