【转】dicom网络通讯入门(2)

约定不等于承诺〃 2022-09-12 01:45 268阅读 0赞

转自:dicom网络通讯入门(2) - assassinx - 博客园

首先我们现一个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 :连接接受协议数据单元,用于对关联请求的应答。
A-Associate-RJ PDU :连接拒绝协议数据单元,用于拒绝关联请求。

A-Release-RQ PDU :连接释放请求协议数据单元,
A-Release-RSP PDU :连接释放响应协议数据单元,
A-Abort PDU
P-DATA-TF PDU :传输内容的pdu(只有这一种)
当通讯双方建立了关联之后,就可以使用P-DATA-TF所提供的传输服务来实现不同的通信功能了。
总之你在进行后面的dimse发送之前先得建立连接,否则你什么也搞不了。
好下面就协商连接的pdu进行分析:

92f9927264d08443152942ef902c0748.gif

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

概述之前,先解释下什么是通讯:
还是炒剩饭 我又得把以前说过的话像背书一样的背一遍了 其实他确实是那么回事。什么是通讯 :
命令tag +数据tag 一起组成sop ,就好象说一句这样的话:“把这根萝卜拿去给我切了” “喏 ,萝卜” 。其实这就是通讯 跟人与人之间传达意思一样。说话的时候太熟练了 没察觉到 你要仔细去想 你自己是一台电脑 ,会是一个什么样的步骤。
网络传输 跟文件组织 是一样的格式 。不过有命令tag 。很多命令tag组合到一堆,这称之为dimse。 c-echo n-create c-find c-store 这些都称之为dimse 。而打印管理是由很多组dimse 包括n-create 那些 你来我往的一套组成 比如 先n-create 什么东西 再 n-set什么东西,他有一种逻辑规范 什么参数错误则不进行n-set。这很多组dimse称之为服务类 ,比如打印管理 就是一个服务类。这些规范在dicom标准的第八章有说明。总之在dimse传递之前 你必须得协商连接。
dicom标准的地址是这个Current Edition,英文的 。看也比较困难 装装面子,主要是理解就行了 我看的也是别人翻译的中文的。不过官方的就是官方的 没办法 某些地方你找不到原因 想参照最标准的指示 你还是得硬着头皮去看英文文档。

Associate pdu 协商连接的过程:
又说多了 不论如何在进行dimse之前必须得进行连接协商 因为你与别人进行通讯首先你得确定两个东西。 谈话的主题是什么(虚拟语法),你是用哪国语言(传输语法)。这两个东西一个称之为虚拟语法 abstract syntax 一个称之为传输语法 transfersyntax 传输语法其实主要确定两个东西 字节序 和 vr表示方式 ,如果你不知道字节序是什么 请自己百度 vr表示方式 跟文件解析一样的,(字节序 和 vr表示方式?)他们两个一起被称之为表达上下文。注意表达上下文可以有多个 每个都有id。如果你是scp端 那么连接协商响应 也就是association-ac的时候你要告知scu,本scp可以完成哪些表达上下文的服务 传输语法是什么,如果服务不了也要给出对应的上下文id 并进行告知。这样的话scu端知道你服务不了就知难而退 主动断开连接。 其次还有些其他东西比如pdu最大数据长度 一般是0x4000。好了讲完了 这就是协商连接的过程 对照上面的图理解了否。
这是官方的解释:

d16206cf54badf8f74267ddd9ec0b325.gif

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

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

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

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

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

发表评论

表情:
评论列表 (有 0 条评论,268人围观)

还没有评论,来说两句吧...

相关阅读