【转】dicom网络通讯入门(3)
转自:
dicom网络通讯入门(3) - assassinx - 博客园
接下来可以进行消息传递了 ,也就是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 PDVset2 { 3 //0x04 P_DATA-TF4 public byte pduType;5 //pdu长度6 public uint pduLen;7 //pdv长度从作用来看他跟上面有所重复 pdv, presentation data value8 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 //header22 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 //items32 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 pdvType47 {48 command, data, commandAndData49 }50 51 struct DataElement52 {53 public uint _tag;54 public WarpedStream.byteOrder bytOrder;55 public bool explicitVR;//是显式VR的 否则隐式VR56 public uint tag57 {58 get { return _tag; }59 set60 {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 else85 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端无data3 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 网络通讯对消息交换的支持
按照他们的套路来 就水到渠成 。
这是我的测试结果 不用怀疑哥的水平 哥是拿到医院去测试过的:
【转】dicom网络通讯入门(3)相关推荐
- 【转】dicom网络通讯入门(2)
转自:dicom网络通讯入门(2) - assassinx - 博客园 首先我们现一个echo响应测试工具,也就是echo 的scu,不是实现打印作业管理么.同学我告诉你还早着呢.本来标题取的就是&l ...
- 【转】dicom网络通讯入门(1)
转自:dicom网络通讯入门(1) - assassinx - 博客园 如果只看标准就会越看越糊涂,根本原因就是因为dicom抽象得太严重,是"专家"弄的.没办法. 那么到底服务类 ...
- 网络工程师入门/底层通讯原理-李强强-专题视频课程
网络工程师入门/底层通讯原理-204人已学习 课程介绍 网络工程师是通过学习和训练,掌握网络技术的理论知识和操作技能的网络技术人员.网络工程师能够从事计算机信息系统的设计.建设.运行和 ...
- 脑残式网络编程入门(三):HTTP协议必知必会的一些知识
为什么80%的码农都做不了架构师?>>> 本文原作者:"竹千代",原文由"玉刚说"写作平台提供写作赞助,原文版权归"玉刚说&q ...
- python sub 不区分大小写_Python网络爬虫入门篇
1. 预备知识 学习者需要预先掌握Python的数字类型.字符串类型.分支.循环.函数.列表类型.字典类型.文件和第三方库使用等概念和编程方法. Python入门篇:https://www.cnblo ...
- DICOM:通讯模型
DICOM:通讯模型 通讯模型 通讯模型 DICOM标准促进了声称符合标准的设备的互操作性.特别是: 解决命令和关联数据的语义.为了使设备进行交互,必须存在有关如何期望设备对命令和相关数据做出反应的标 ...
- Java DICOM 网络传输_DICOM医学图像处理:fo-dicom网络传输之 C-Echo and C-Store
背景: 上一篇博文对DICOM中的网络传输进行了介绍,主要参照DCMTK Wiki中的英文原文.通过对比DCMTK与fo-dicom两个开源库对DICOM标准的具体实现,对理解DICOM标准有一个更直 ...
- Android网络编程入门解析
网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编 ...
- day06--java高级编程:多线程,枚举类,注解,反射,网络通讯
1 Day16–多线程01 1.1 程序概念 程序(program):是为完成特定任务.用某种语言编写的一组指令的集合.即指一段静态的代码,静态对象. 1.2 进程 1.2.1 概念 进程(proce ...
最新文章
- Ubuntu 10.10系统安装手记
- javascript组件_是的,JavaScript运行Swift。 无论如何都要构建您的组件库。
- 没有点真的大项目工程经验,你真以为能进大厂的算法岗?
- 计算机的学生该怎么做?
- boost::gil::channel_view用法的测试程序
- Layui表格之多列合并展示
- php cdi_DI / CDI –基础
- Linux之Ubuntu下安装屏幕录像软件(SimpleScreenRecorder)【摘抄】
- 【JavaWeb】JavaScript基础篇+高级篇
- PostgreSQL与MySQL的日期类型DATE/date的格式区别
- 读取iOS plist文件 (其实类似读取xml文件)
- CentOS 上snmp的安装和配置
- 顶点计划:520日讨论
- sklearn报错 ImportError: No module named externals 问题解决
- 传阿里云盘将开启扩容收费测试:200GB售价 108元/年
- 电脑COM串口管理芯片75232、75185及电路(两者可代换)
- ps如何把自己的图与样机结合_如何利用PS制作贴图样机那
- 2021-03-19我的博客
- 观点| 如何避免GitHub那样断网43秒瘫痪 24 个小时?
- UTC时间与北京时间相互转换
热门文章
- 01背包、完全背包、多重背包
- Android应用的安全的攻防之战
- oracle中如何创建dblink
- 关闭弹出窗体,刷新父页面
- SQL SERVER 的SQL语句优化方式小结
- Linq 学习笔记(二)
- Java程序员的推荐阅读书籍
- 湖北省汉十高速公路项目接近尾声,所想所感真的值得写写
- [剑指offer][JAVA]面试题第[06]题[从尾到头打印链表][栈][递归]
- 九歌计算机在线作诗硬件原理,“九歌”作诗是如何炼成的?