接下來能夠進行消息傳遞了 ,也就是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也有 對應的 他就是 :ui
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 。this
好了開工吧,打住 不是說還有服務類規範麼 ,只有複雜的纔有服務類規範 咱們這個echo是很是很是很是之簡單的 因此沒有服務類規範 直接開動吧:spa
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標準跟網絡通信相關的幾個章節 :code
DICOM Part 4: Service Class Specifications ,服務類規範orm
DICOM Part 7: Message Exchange 消息交換blog
DICOM Part 8: Network Communication Support for Message Exchange 網絡通信對消息交換的支持遞歸
按照他們的套路來 就水到渠成 。
這是個人測試結果 不用懷疑哥的水平 哥是拿到醫院去測試過的: