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标准第八章 33页。你可能又要问 dimse又是什么 ,dimse全称 dicom 消息服务元素DIMSE(DICOM Message Service Element)
为什么你就说echo属于dimse 。请看dicom标准第七章 的目录: 9     DIMSE-C        9.1.5      C-ECHO SERVICE 。不用我多说了噻。
在这之前还是像以前一样先把tf-pdu的数据结构跟序列化搞了吧:

  1 struct PDVset
  2     {
  3         //0x04
  4         public byte pduType;
  5         //pdu长度
  6         public uint pduLen;
  7         //pdv长度从作用来看他跟上面有所重复 pdv, presentation data value
  8         public uint itemLen;
  9         //这个contextID其实是指协商连接时的presentation context id
 10         //最好判断下是否跟协商时的一致
 11         public byte contextID;
 12         //消息控制头 确定pdv类型是command 还是data 发送完成与否
 13         public byte msgControlHeader;
 14
 15         public SortedDictionary<uint, DataElement> elements;
 16
 17         public byte[] serial()
 18         {
 19             if ((pduLen != 0 && itemLen != 0) == false)
 20                 return null;
 21             //header
 22             MemoryStream _stream = new MemoryStream((int)pduLen + 6);
 23             WarpedStream stream = new WarpedStream(_stream);
 24
 25             stream.writeByte(0x04);
 26             stream.skip_write(1);
 27             stream.writeUint(pduLen);
 28             stream.writeUint(itemLen);
 29             stream.writeByte(contextID);
 30             stream.writeByte(msgControlHeader);
 31             //items
 32             foreach (DataElement item in elements.Values)
 33             {
 34                 stream.writeBytes(item.serial());
 35             }
 36
 37             _stream.Flush();
 38
 39             byte[] data = new byte[_stream.Length];
 40             Array.Copy(_stream.GetBuffer(), data, _stream.Length);
 41             stream.close();
 42             _stream.Close();
 43             return data;
 44         }
 45     }
 46     enum pdvType
 47     {
 48         command, data, commandAndData
 49     }
 50
 51     struct DataElement
 52     {
 53         public uint _tag;
 54         public WarpedStream.byteOrder bytOrder;
 55         public bool explicitVR;//是显式VR的 否则隐式VR
 56         public uint tag
 57         {
 58             get { return _tag; }
 59             set
 60             {
 61                 _tag = value;
 62                 VR = VRs.GetVR(value);
 63                 uint _len = VRs.getLen(VR);
 64                 if (_len != 0)
 65                     len = _len;
 66             }
 67         }
 68         public ushort VR;
 69         //虽然长度为uint 但要看情况隐式时都是4字节 显式时除ow那几个外都是2字节
 70         //如果为ow 显示不但长度为4 在之前还要跳过2字节,除ow那几个之外不用跳过
 71         public uint len;
 72         public byte[] value;
 73         public IList<DataElement> items ;//子项
 74         public bool haveItems;//是否包含子项
 75
 76         //值的显示
 77         public string showValue()
 78         {
 79             if (haveItems )
 80                 return null;
 81
 82             if (value != null)
 83                 return Tags.VFdecoding(VR, value, bytOrder);
 84             else
 85                 return null;
 86         }
 87         //赋值
 88         public void setValue(string valStr)
 89         {
 90             if (haveItems )
 91                 return;
 92
 93             if (VRs.IsStringValue(VR)) {
 94                 len = (uint)valStr.Length;
 95                 value = Tags.VFencoding(VR, valStr, bytOrder, len);
 96             }
 97             else if (len != 0)//就是这个破地方 因为element的连续使用 导致会截断字符串
 98                 value = Tags.VFencoding(VR, valStr, bytOrder, len);
 99             else
100             {
101                 value = Tags.VFencoding(VR, valStr, bytOrder);
102                 if (VRs.IsStringValue(VR))
103                     len = (uint)value.Length;
104             }
105         }
106         //序列化
107         public byte[] serial()
108         {
109             MemoryStream data_serial = new MemoryStream();
110             serial(this, data_serial);
111             byte[] data = new byte[data_serial.Length];
112             Array.Copy(data_serial.GetBuffer(), data, data.Length);
113             data_serial.Close();
114             return data;
115         }
116         //序列化的递归调用
117         public void serial(DataElement element, MemoryStream data_serial)
118         {
119             //int len_serial = element.getSerialLen();
120             //if ((VR == VRs.SQ && len_serial == UInt32.MaxValue) || (tag == 0xfffee000 && len == UInt32.MaxValue))//靠 遇到文件夹开始标签了
121             if (element.haveItems )
122             {
123                 //开始标记
124                 data_serial.WriteByte((byte)((element._tag & 0x00ff0000) >> 16));
125                 data_serial.WriteByte((byte)((element._tag & 0xff000000) >> 24));
126                 data_serial.WriteByte((byte)(element._tag & 0x000000ff));
127                 data_serial.WriteByte((byte)((element._tag & 0x0000ff00) >> 8));
128                 data_serial.WriteByte(0xff); data_serial.WriteByte(0xff); data_serial.WriteByte(0xff); data_serial.WriteByte(0xff);
129
130                 foreach (DataElement item in element.items)
131                 {
132                     item.serial(item, data_serial);
133                 }
134
135                 //结束标记
136                 if (element.VR == VRs.SQ)
137                 {
138                     data_serial.WriteByte((byte)((0xfffee0dd & 0x00ff0000) >> 16));
139                     data_serial.WriteByte((byte)((0xfffee0dd & 0xff000000) >> 24));
140                     data_serial.WriteByte((byte)(0xfffee0dd & 0x000000ff));
141                     data_serial.WriteByte((byte)((0xfffee0dd & 0x0000ff00) >> 8));
142                 }
143                 else
144                 {
145                     data_serial.WriteByte((byte)((0xfffee00d & 0x00ff0000) >> 16));
146                     data_serial.WriteByte((byte)((0xfffee00d & 0xff000000) >> 24));
147                     data_serial.WriteByte((byte)(0xfffee00d & 0x000000ff));
148                     data_serial.WriteByte((byte)((0xfffee00d & 0x0000ff00) >> 8));
149                 }
150                 data_serial.WriteByte(0x00); data_serial.WriteByte(0x00); data_serial.WriteByte(0x00); data_serial.WriteByte(0x00);
151             }
152             else
153             {
154                 byte[] data = new byte[element.getSerialLen()];
155                 uint _len = element.len;
156                 if (_len % 2 != 0)
157                     _len++;
158                 if (element.VR == VRs.SQ)
159                     _len = 0xffffffff;
160
161                 data[0] = (byte)((element._tag & 0x00ff0000) >> 16);
162                 data[1] = (byte)((element._tag & 0xff000000) >> 24);
163                 data[2] = (byte)(element._tag & 0x000000ff);
164                 data[3] = (byte)((element._tag & 0x0000ff00) >> 8);
165
166                 if (element.explicitVR)//显示VR
167                 {
168                     data[4] = 0x00;
169                     data[5] = 0x00;
170                     if (VRs.IsLengthField16Bit(VR))
171                     {
172                         if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
173                         {
174                             data[6] = (byte)(_len & 0x000000ff);
175                             data[7] = (byte)((_len & 0x0000ff00) >> 8);
176                         }
177                         else
178                         {
179                             data[6] = (byte)((_len & 0x0000ff00) >> 8);
180                             data[7] = (byte)(_len & 0x000000ff);
181                         }
182
183                         for (int i = 0; i < element.value.Length; i++)
184                             data[8 + i] = element.value[i];
185                     }
186                     else
187                     {
188                         if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
189                         {
190                             data[6] = (byte)((_len & 0xff000000) >> 24);
191                             data[7] = (byte)((_len & 0x00ff0000) >> 16);
192                             data[8] = (byte)((_len & 0x0000ff00) >> 8);
193                             data[9] = (byte)(_len & 0x000000ff);
194
195                         }
196                         else
197                         {
198                             data[6] = (byte)(_len & 0x000000ff);
199                             data[7] = (byte)((_len & 0x0000ff00) >> 8);
200                             data[8] = (byte)((_len & 0x00ff0000) >> 16);
201                             data[9] = (byte)((_len & 0xff000000) >> 24);
202                         }
203                         if (element.value == null)
204                             throw new Exception(string.Format("异常:tag{0} value未赋值 ", Tags.ToHexString(element.tag)));
205
206                         for (int i = 0; i < element.value.Length; i++)
207                             data[10 + i] = element.value[i];
208                     }
209                     //len_ser = (int)(4 + 2 + 4 + len);
210                     //len_ser = (int)(4 + 2 + len);
211                 }
212                 else //隐式Vr
213                 {
214                     if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
215                     {
216                         data[4] = (byte)((_len & 0xff000000) >> 24);
217                         data[5] = (byte)((_len & 0x00ff0000) >> 16);
218                         data[6] = (byte)((_len & 0x0000ff00) >> 8);
219                         data[7] = (byte)(_len & 0x000000ff);
220                     }
221                     else
222                     {
223                         data[4] = (byte)(_len & 0x000000ff);
224                         data[5] = (byte)((_len & 0x0000ff00) >> 8);
225                         data[6] = (byte)((_len & 0x00ff0000) >> 16);
226                         data[7] = (byte)((_len & 0xff000000) >> 24);
227
228                     }
229                     if (element.value == null)
230                         throw new Exception(string.Format("异常:tag{0} value未赋值 ", Tags.ToHexString(element.tag)));
231
232                     for (int i = 0; i < element.value.Length; i++)
233                         data[8 + i] = element.value[i];
234                 }
235                 data_serial.Write(data, 0, data.Length);
236             }
237         }
238
239         //获取单个元素序列化长度的递归调用
240         public void getSerialLen_item(DataElement element, ref int len)
241         {
242             if (element.haveItems)
243             {
244                 len += element.getHeaderLen();
245                 foreach (DataElement item in element.items)
246                     getSerialLen_item(item, ref len);
247                 len += 8;
248             }
249             else
250             {
251                 if (element.value != null)
252                     len += element.getHeaderLen() + element.value.Length;
253                 else
254                     len += element.getHeaderLen();
255             }
256             if (len % 2 != 0)//文件元信息元素整体字节数一定是偶数(包括tag VR 数据长度 数据 这些一起)
257                 len++;
258         }
259
260         //获取序列化后整个元素的长度
261         public int getSerialLen()
262         {
263             int serial_len=0;
264             getSerialLen_item(this, ref serial_len);
265             return serial_len;
266         }
267
268         //获取item的header长度
269         public int getHeaderLen()
270         {
271             int len_ser = 0;
272             int len_tmp = 0;
273             if (explicitVR)//显示VR
274             {
275                 if (tag == 0xfffee000 || tag == 0xfffee00d || tag == 0xfffee0dd)
276                     len_ser = 4 + 4;
277                 else if (VR == VRs.OB || VR == VRs.OW || VR == VRs.OF ||
278                         VR == VRs.UT || VR == VRs.SQ || VR == VRs.UN)
279                     len_ser = (int)(4 + 2 + 4 + len_tmp);
280                 else
281                     len_ser = (int)(4 + 2 + len_tmp);
282             }
283             else //隐式Vr
284             {
285                 len_ser = (int)(4 + 4 + len_tmp);
286             }
287
288             return len_ser;
289         }
290     }

不要问我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.10008.1.1";

为啥是这个 我不会再说了 你懂的。
command field 
这个是作甚的,他的作用是用来区分不同的dimse 比如 c-create  c-find ,不用我多讲了 旁边的说明已经很明显了 echo时他的值应设置成0x0030  。
command data set type
数据集选项 ,说白了就是给个标识 后面有无数据集,我们这里自然是没有 那么应设置成0x0101 。

好了开工吧,打住 不是说还有服务类规范么 ,只有复杂的才有服务类规范 我们这个echo是非常非常非常之简单的 所以没有服务类规范 直接开动吧:

 1 //组织Verification_CECHORSP响应原语
 2         //rq端无data ,rsp端无data
 3         public void Verification_CECHORQ()
 4         {
 5             PDVset rq = new PDVset();
 6             rq.pduType = 0x04;
 7             rq.contextID = pstContextId;
 8             rq.msgControlHeader = 0x03;
 9             rq.elements = new SortedDictionary<uint, DataElement>();
10
11             int len = 0;
12
13             DataElement element = new DataElement();
14             element.bytOrder = bytOrder;
15
16             element.tag = 0x00000002;
17             element.setValue(UIDs.Verification);
18             rq.elements.Add(0x00000002, element);
19             len += (element.getSerialLen());
20
21             element.tag = 0x00000100;
22             element.setValue(0x0030.ToString());
23             rq.elements.Add(0x00000100, element);
24             len += (element.getSerialLen());
25
26             element.tag = 0x00000110;
27             element.setValue(0x03.ToString());
28             rq.elements.Add(0x00000110, element);
29             len += (element.getSerialLen());
30
31             element.tag = 0x00000800;//有无对应的数据段
32             element.setValue(0x0101.ToString());
33             rq.elements.Add(0x00000800, element);
34             len += (element.getSerialLen());
35
36
37             element.tag = 0x00000000;//消息原语数据长度
38             element.setValue(len.ToString());
39             rq.elements.Add(0x00000000, element);
40             //len += (element.getSerialLen());
41
42             rq.itemLen = (uint)(12 + 2 + len);
43
44             rq.pduLen = rq.itemLen + 4;
45
46             //进行c-echo-rsp响应
47             stream.writeBytes(rq.serial());
48         }

看看代码里面特定的地方 是不是跟我上面描述的一样?就这样so easy  。看到没 其实我这些都是在dicom文档里翻的 就这样而已没什么神奇的 相信你也能。再来复习下dicom标准跟网络通讯相关的几个章节 :

DICOM Part 4: Service Class Specifications  ,服务类规范

DICOM Part 7: Message Exchange 消息交换

DICOM Part 8: Network Communication Support for Message Exchange 网络通讯对消息交换的支持

按照他们的套路来 就水到渠成 。

这是我的测试结果 不用怀疑哥的水平 哥是拿到医院去测试过的:

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

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

dicom网络通讯入门(2)

转自:http://www.cnblogs.com/assassinx/p/3649498.html 第二篇,前面都是闲扯 其实正文现在才开始,这次是把压箱底的东西都拿出来了. 首先我们今天要干的事是实现一个echo响应测试工具 也就是echo 的scu,不是实现打印作业管理么.同学我告诉你还早着呢.本来标题取的就是<dicomviewer 第二弹 之 实现打印管理>名字多霸气,最后我又改回来了. 首先你得把数据组织方式搞懂 那就是pdu  和dimse  元素  数据元素.然后基于这之上你得

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对象.设置链接参数.链接到服务器.向服务器写数据.从服务器读取