dicom網絡通信入門(2)

第二篇,前面都是閒扯 其實正文如今纔開始,此次是把壓箱底的東西都拿出來了。 首先咱們今天要乾的事是實現一個echo響應測試工具 也就是echo 的scu,不是實現打印做業管理麼。同窗我告訴你還早着呢。原本標題取的就是《dicomviewer 第二彈 之 實現打印管理》名字多霸氣,最後我又改回來了。
 
首先你得把數據組織方式搞懂 那就是pdu  和dimse  元素  數據元素。而後基於這之上你得把協商鏈接這塊搞懂 ,協商鏈接都沒經過不用說後面的了。而後你得把實現一個功能 好比打印 ,scu跟scp之間你來我往的 過程和概念搞懂 也就是dimse 而後纔是服務類。最夠你全都理解了 而且寫出東西來了能跟醫院的設備正常 鏈接和操做了,那麼恭喜你 差很少了。html


最後要說的是: 解析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。好了講完了 這就是協商鏈接的過程 對照上面的圖理解了否。
這是官方的解釋:
app

官方的解釋 網絡協議是分層的,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的網絡序列化函數:socket

  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 併發送:ide

 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服務。工具

相關文章
相關標籤/搜索