Java DICOM 网络传输_DICOM医学图像处理:fo-dicom网络传输之 C-Echo and C-Store
背景:
上一篇博文對DICOM中的網絡傳輸進行了介紹,主要參照DCMTK Wiki中的英文原文。通過對比DCMTK與fo-dicom兩個開源庫對DICOM標準的具體實現,對理解DICOM標準有一個更直觀的認識。此篇博文是對上一篇博文的補充,因為專欄前面的示例大多是利用DCMTK工具包來進行的,此次借著分析fo-dicom源碼結構的機會,參照fo-dicom的README.md,給出C-ECHO 和C-STORE服務的具體實現。在實現的同時給出DICOM3.0標準中的相關介紹,幫助我們理解。
C-ECHO的fo-dicom實現:
1)C-ECHO參數說明:
C-ECHO又叫驗證服務(即Verification),是用來驗證DICOM服務兩端的交流是否暢通。DICOM3.0的第7部分給出了C-ECHO服務的參數,如下圖1所示:
【注意】:這里講解一下DICOM3.0標準的閱讀方法。以DICOM3.0標準的第7、8部分為例,【第7部分】中第9章開始講解DIMSE-C的各種服務,依次為C-STORE、C-FIND、C-GET、C-MOVE、C-ECHO(上圖1就是我在該部分的C-ECHO小節中截取的),其中前半部分主要給出了DIMSE-C各種服務的參數,這里僅僅是羅列出DICOM3.0標準的要求,目的是讓你明白各個服務參數是否是必要的(分別用M、U、=表示);后半部分開始講解DIMSE-C各種服務的協議及實現流程(即Protocol和Procedures),在PROTOCOL中給出的是具體的DIMSE-C服務的各種指令在傳輸過程中的格式,該部分也就是你利用抓包工具能夠直接抓取的真實數據流;在Procedures中給出的是SCU和SCP之間的交互流程,通常為了說明服務是由誰發起的,由誰響應。在介紹Protocol的時候對于比較復雜的、可變的區域(Variables Fields)通常會放在附錄中,例如第7部分的附錄C和E等;【第8部分】與【第7部分】類似,從第7章開始介紹ACSE的各種服務的參數(如下圖2所示),依次為A-ASSOCIATE、A-RELEASE、A-ABORT、A-P-ABORT、P-DATA;第9章給出的是ACSE中各種服務的結構,即STRUCTURE,該部分與【第7部分】中的PROTOCOL相同,給出的是具體ACSE PDU在傳輸時刻的數據格式,該部分也是可以通過抓包工具直接獲得的;同樣對于比較復雜的STRUCTURE介紹也會單獨放到附錄中,例如第8部分的附錄E。
fo-dicom對于DIMSE消息的實現基類是DicomMessage,針對請求和響應分別派生出了DicomRequest和DicomResponse,最后根據不同的DIMSE服務派生相應的類。C-ECHO是其中最簡單的,fo-dicom已經給出了SCP和SCU的具體實現。參照fo-dicom中的README.md文件,給出C-ECHO SCP和SCU的代碼,詳情如下:
2)C-ECHO代碼實例:
C-ECHO SCP的代碼是直接利用了fo-dicom給出的DicomCEchoProvider類,通過創建DicomServer(12345)對象,開啟C-ECHO SCP服務,其中參數12345表示C-ECHO服務的端口號。C-ECHO SCU和C-ECHO SCP的代碼分別如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Dicom;
using Dicom.Network;
namespace CEchoSCU
{
class Program
{
static void Main(string[] args)
{
var client = new DicomClient();
client.NegotiateAsyncOps();
client.AddRequest(new DicomCEchoRequest());
client.Send("127.0.0.1", 12345, false, "SCU", "ANY-SCP");
Console.ReadLine();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using Dicom;
using Dicom.Network;
namespace CEchoSCP
{
class Program
{
static void Main(string[] args)
{
var server = new DicomServer(12345);
Console.ReadLine();
}
}
}
實際運行結果如下:
C-STORE的fo-dicom實現:
1)C-STORE參數說明:
C-STORE就是存儲服務,在醫療信息系統中最常見的服務之一,尤其是PACS系統中。與C-ECHO服務相同,DICOM3.0標準第7部分也給出了C-STORE服務的參數列表,如下圖4所示:
該參數列表的目的同樣是為了介紹C-STORE服務中各參數的必要性,真正的參數消息格式在后續的C-STORE PROTOCOL中介紹,如下圖5所示:
圖5中給出的僅僅是C-STORE RQ的實際消息格式,該消息由C-STORE服務的SCU(客戶端)流向C-SOTRE服務的SCP(服務端);與之相對應的C-STORE-RSP消息是從SCP流向SCU,DICOM3.0標準中也有C-STORE-RSP的詳細介紹,如下圖6所示。
2)C-STORE代碼實例:
在fo-dicom的說明文檔README.md中只給出了C-STORE的SCU示例,如下圖7所示:
上一篇博文對fo-dicom源碼結構分析的基礎上可知,實現DIMSE眾多服務的SCU端很容易,首先創建DicomClient實體類,代表一個客戶端,然后通過AddRequest添加不同的請求即可實現各種DIMSE的客戶端,如圖7中C-STORE SCU的實現為:
client.AddRequest(new DicomCStoreRequest(@"test.dcm"));
DicomCStoreRequest類是DicomRequest的派生類,上述代碼通過制定DCM文件路徑來構建了一個DicomCStoreRequest對象,在DicomCStoreRequest內部通過打開指定的DCM文件提取獲得上述參數中的Affected SOP Instance UID等參數。
既然fo-dicom中沒有提供線程的C-STORE SCP實現,我們先利用DCMTK的storescp.exe工具來驗證一下fo-dicom給出的C-STORE SCU的正確性,測試代碼如下:
SCP端利用storescp.exe,在控制臺下輸入:storescp.exe –d –od c:\ 12345
SCU端利用fo-dicom中的C-STORE SCU,具體代碼如上圖7所示,然后雙擊生成后的storescu.exe。
最后可以得到如下結果,如圖8所示:
同時在C盤根目錄下可以看到被重命名的test.dcm文件,如下圖9所示:
之所以被重命名我們在之前分析DCMTK開源庫源碼時提到過,通常DCMTK會根據SOP Instance UID(-uf,默認的)對接收到的DCM文件進行重命名,當然也可以通過選項設置重命名的方式,例如按照時間(-tn)、特定前綴(-fe)等等,如下圖10所示。
由此說明fo-dicom中給出的C-STORE SCU功能正常,接下來我們嘗試利用fo-dicom構建C-STORE SCP。
3)構建C-STORE SCP
打開C-ECHO SCP的實現DicomCEchoProvider.cs文件,我們看到DicomCEchoProvider類通過派生DicomService服務類來實現了Dicom服務的基本框架,然后通過實現IDicomServiceProvider和IDicomCEchoProvider接口,完成了C-ECHO 的服務端,仔細查看DicomCEchoProvider的代碼可以發現,其實就是在接收到A-ASSOCIATE-RQ消息后,判別Presentation Context中的Abstract Syntax,根據實際請求消息來決定是否建立連接,另外當接收到C-ECHO SCU發起的C-ECHO Request時,向其會送DicomCEchoResponse確認信息即可。
既然通過實現兩個接口函數就可以完成C-ECHO SCP的構建,那么我們就自己嘗試來完成C-STORE SCP的搭建,仿照DicomCEchoProvider的方式,DicomCStoreProvider的代碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dicom;
using Dicom.Log;
using Dicom.Network;
using System.Threading;
using System.IO;
namespace CStoreSCP
{
class CStoreSCPProvider : DicomService, IDicomServiceProvider, IDicomCStoreProvider
{
public CStoreSCPProvider(Stream stream, Logger log) : base(stream, log) { }
public DicomCStoreResponse OnCStoreRequest(DicomCStoreRequest request)
{
return new DicomCStoreResponse(request,DicomStatus.Success);
}
public void OnCStoreRequestException(string tempFileName, Exception e)
{
}
public void OnReceiveAssociationRequest(DicomAssociation association)
{
foreach (var pc in association.PresentationContexts)
{
if (pc.AbstractSyntax == DicomUID.Verification)
pc.SetResult(DicomPresentationContextResult.Accept);
else
{
//pc.SetResult(DicomPresentationContextResult.RejectAbstractSyntaxNotSupported);
}
if (pc.AbstractSyntax == DicomUID.CTImageStorage)
{
pc.SetResult(DicomPresentationContextResult.Accept);
}
}
SendAssociationAccept(association);
}
public void OnReceiveAssociationReleaseRequest()
{
SendAssociationReleaseResponse();
}
public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason)
{
}
public void OnConnectionClosed(int errorCode)
{
}
}
}
然后通過var server = new DicomServer(12345);Console.ReadLine(); 來構建一個C-STORE SCP應用。
下圖11是先運行CStoreSCP.exe,然后運行CStoreSCU.exe得到的結果:
從圖11的輸出結果可以看出,此次C-STORE SCP和SCU兩端的通訊順利完成,那么我們發送的C:\test.dcm文件會被CStoreSCP.exe存儲到那里呢?由上一篇博文分析我們知道fo-dicom庫中將DICOM的服務基本框架放在了DicomService類中,查看其中處理P-DATA服務的核心函數ProcessPDataTF,可以看到如下代碼:
var file = new DicomFile();
file.FileMetaInfo.MediaStorageSOPClassUID = pc.AbstractSyntax;
file.FileMetaInfo.MediaStorageSOPInstanceUID = _dimse.Command.Get(DicomTag.AffectedSOPInstanceUID);
file.FileMetaInfo.TransferSyntax = pc.AcceptedTransferSyntax;
file.FileMetaInfo.ImplementationClassUID = Association.RemoteImplemetationClassUID;
file.FileMetaInfo.ImplementationVersionName = Association.RemoteImplementationVersion;
file.FileMetaInfo.SourceApplicationEntityTitle = Association.CallingAE;
_dimseStream = CreateCStoreReceiveStream(file);
轉到CreateCStoreReceiveStream函數內部,通過函數的說明就可以知道fo-dicom對C-STORE服務默認情況下是在系統中創建了一個臨時文件,用來接收C-STORE SCU的數據,因此可以推斷我們的test.dcm文件應該也在臨時文件夾中,打開我本機的temp文件夾,可以看到有一個后綴為tmp的臨時文件,如下圖12所示。文件大小與我們測試用的test.dcm相同,嘗試修改.tmp的擴展名,修改后可以使用DICOM Viewer軟件正常打開,因此說明我們的C-STORE SCP順利成功。
DICOM數據流分析:
C-ECHO服務數據流分析:
1)工具:
在本地測試,為了抓取127.0.0.1回路數據包,需要使用RawCap.exe工具包。RawCap.exe是控制臺程序,在抓取本地回路數據包時很便捷。當抓取完成后我們需要借助于WireShark的強大分析功能,來實現C-ECHO數據流的詳細分析,WireShark可以直接打開RawCap.exe抓取的.pcap數據包。
WireShark是功能強大的數據包統計分析工具,當然本身也可以抓取網絡數據包(本地回路數據包不方便)。WireShark支持眾多協議,其中包括DICOM協議。下面以C-ECHO的數據包為例,簡單介紹一下如何使用WireShark來自動識別并解析DICOM數據包。首先打開抓取的本地C-ECHO數據包cecho.pcap。如圖13,在Protocol中右鍵選擇"Protocol Preferences “中的"Data Preferences…”,會彈出一個協議設置窗口如圖13。在左側列表中找到DICOM協議,勾選圖14中紅色部分。該部分的意思是除了檢測DICOM協議默認端口104的數據包的同時也檢測其他端口的數據包。之所以需要選擇此項是因為很多DICOM服務并未使用協議默認的104端口。設置完成后,重新查看Protocol列,可以看到出現了DICOM字樣,如圖15所示,最上方的帶DICOM字樣的數據包就是我們抓取到的C-ECHO服務的本地回路數據包。
2)C-ECHO數據流分析:
利用RawCap.exe和WireShark兩大強大的工具,我們已經可以直觀的看到抓取的DICOM數據包了,接下來我就按照DICOM標準第7部分和第8部分中的內容,逐個數據包來分析一下,通過觀察真實的數據包來加深一下對DICOM協議的理解。
從圖15中可以看到,最頂部DICOM協議包含6個數據包,分別是連接建立(A-ASSOCIATE RQ/A-ASSOCIATE AC)、數據交互(P-DATA-TF)、連接釋放(A-RELEASE RQ/A-RELEASE RP),這與DICOM協議第8部分中介紹的ACSE控制流程相符。
A-ASSOCIATE RQ/A-ASSOCATE AC分析:
雙擊第一個DICOM數據包,該數據包是A-ASSOCIATE RQ的真實數據流,如圖16所示:
按照DICOM協議第8部分中第9章對A-ASSOCIATE RQ PDU的描述,我們來逐項對比(DICOM協議可參照圖17):第一項1個字節的PDU-type,圖中為01H,說明該數據包代表的是A-ASSOCIATE RQ;第二項一個字節的保留,數據流為00H;第三項是四個字節的PDU-length,圖中為00 00 00 ff,轉換為無符號整數正好為255,這也是整個圖中藍色部分后續的數據包長度;第四項是兩個字節的Protocol-Version,圖中為00 01,對應版本為1;第五項為兩字節保留;第六項和第七項是我們熟悉的AE Title,從WireShark的數據流中也可以看出分別是ANY-SCP和ECHOSCU;第8項又是一堆保留字節,用00H填充;第9項是一個可變區域(Variable Fields),該項是復合項,內部包含多個獨立的子項。由圖16可以看出該復合項內部含有Application Context、Presentation Context(2個,ID分別是1、3)、UserInfo三個子項;而UserInfo又是一個復合項,其內部又包含了Max PDU Length、ImplentationUID、ImplentationVersion三個子項。從WireShark的分析來看,Application Context子項類型為10H、Presentation Context子項類型為20H、UserInfo子項為50H(其內部的嵌套子項的類型分別為,Max PDU Length-51H、Implentation UID-52H、Implentation Version-55H)。各個子項的類型與DICOM協議第7、8兩部分中的附錄D相對應,例如圖19中我截取的是Max PDU Length子項的格式。A-ASSOCIATE AC的數據包分析與A-ASSOCIATE RQ類似,只是A-ASSOCIATE AC的數據流更簡單一些,這里就不做詳細介紹了。(最終數據域DICOM協議的對應結果如圖18)。
A-RELEASE RQ/A-RELEASE RP分析:
連接釋放的數據包格式簡單,下面圖20和圖21分別是DICOM協議第8部分中給出的連接釋放請求和應答數據包的格式:
雙擊WireShark中的連接釋放數據包,可以看到兩者的數據包類型分別為05H和06H,這與上圖中DICOM協議的規定完全一致。
P-DATA-TF:
在上一篇博文中(http://blog.csdn.net/zssureqh/article/details/41016091)我已經分析了,DICOM協議第7部分中規定的DIMSE消息(Command和Dataset)是通過第8部分中ACSE協議中的P-DATA-TF服務以PDV的形式來傳輸的。下面就讓我們來分析一下DIMSE消息中C-ECHO RQ 和C-ECHO RSP的格式:
雙擊WireShark數據包中間兩個,從數據流向可以斷定一個是C-ECHO RQ消息,一個是C-ECHO RSP消息。先打開第一個,按照上一篇博文的分析,首先該數據包是一個P-DATA-TF PDU,因此需要符合下圖23中的格式。
通過分析最外層的是代表P-DATA-TF類型的04H,然后是由DIMSE消息填充的PDV區域,該項是復合項,第一子項是Item-length,此處為46H;第二子項為Presentation-context-ID,此處為01H;第三子項又是一個復合項,是DICOM標準第4部分中給出的DIMSE消息結構,包括Message Control Header、Command和DataSet三部分。此處的MessageControlHeader為03H,即表示是Command數據而不是DataSet,且是最后一個PDV,即Last Fragment。具體的對應關系如圖24所示:
C-STORE服務數據流分析:
1)工具:
依然使用RawCap.exe+WireShark來解決。
2)C-STORE數據流分析:
按照C-ECHO中的分析方式,同樣可以看到DICOM數據包,如圖25所示:
A-ASSOCIATE RQ/A-ASSOCIATE AC:
對于A-ASSOCIATE RQ/A-ASSOCIATE AC的分析與C-ECHO中基本類似,唯一不同的就是對于C-STORE服務需要不同的Presentation Context描述上下文,如圖26所示,此處C-STORE需要的是CT Image Storage服務,其SOP Class UID為1.2.840.10008.5.1.4.1.1.2。
A-RELEASE RQ/A-RELEASE RP:
與C-ECHO中的相同,這也說明了博文中的C-ECHO 和C-STORE服務實現成功,連接能夠正常釋放。
P-DATA-TF:
此處著重分析一下C-STORE服務中的P-DATA-TF數據包,因為傳輸一個DCM文件需要多個PDU,自然也需要多個PDV。所以我們通過分析C-STORE的P-DATA-TF數據包可以更形象的學習Message Control Header和DIMSE的知識。
同樣傳輸的每個數據包首先符合P-DATA-TF的格式要求,第一項是PDU類型,即04H;隨后是保留項、PDU-length、PDV復合項……,這與C-ECHO中的分析相同。按照上一篇博文的分析,C-STORE PROTOCOL的流程是CSTORE SCU向SCP發送C-STORE RQ消息,但是打開圖中的第一個P-DATA數據包時我們看到的卻不是C-STORE RQ,而是其中的一個數據片段,如下圖27所示。
依次查看后面的幾個P-DATA數據包,都是類似的情況。最后倒數兩個分別是C-STORE RQ中DCM文件數據的最后一個數據包(Last Fragment)和SCP向SCU發送的C-STORE RSP,具體分析如圖28所示:
從最后數據包Command中的(0000,0100)的值域8001H可知該指令就是C-STORE RSP。
看到這里你或許會很興奮,因為我們終于也看到了C-STORE服務的真實數據流,但是在上圖中的所有DICOM對應的數據包中我們并未找到C-STORE SCU發起的C-STORE RQ數據包,那么C-STORE RQ數據包在哪里呢?
讓我們將cstore.pcap的所有數據包按照時間排序,出現了大量標記為[TCP segment of a reassembled PDU]的TCP數據包。
打開第一個標記為[TCP segment of a reassembled PDU]的TCP數據包,其內部的真實數據分析如下圖30所示:
至此我們順利找到了C-STORE SCU端發送的C-STORE RQ消息,之所以沒有在WireShark中以DICOM協議顯示,可能是由于WireShark在識別多個連續分片的數據時不夠智能。博文中的示例圖和文字較多,仔細閱讀后應該對DICOM3.0中的協議會有更進一步的了解。通過分析數據包的方式在更直觀的學習和掌握DICOM3.0標準的同時,對后期排查DICOM網絡傳輸相關錯誤也會有幫助。
備注:
再次說明一下閱讀DICOM3.0標準的方式:
以DICOM3.0標準的第7、8部分為例,【第7部分】中第9章開始講解DIMSE-C的各種服務,依次為C-STORE、C-FIND、C-GET、C-MOVE、C-ECHO(上圖1就是我在該部分的C-ECHO小節中截取的),其中前半部分主要給出了DIMSE-C各種服務的參數,這里僅僅是羅列出DICOM3.0標準的要求,目的是讓你明白各個服務參數是否是必要的(分別用M、U、=表示);后半部分開始講解DIMSE-C各種服務的協議及實現流程(即Protocol和Procedures),在PROTOCOL中給出的是具體的DIMSE-C服務的各種指令在傳輸過程中的格式,該部分也就是你利用抓包工具能夠直接抓取的真實數據流;在Procedures中給出的是SCU和SCP之間的交互流程,通常為了說明服務是由誰發起的,由誰響應。在介紹Protocol的時候對于比較復雜的、可變的區域(Variables Fields)通常會放在附錄中,例如第7部分的附錄C和E等;【第8部分】與【第7部分】類似,從第7章開始介紹ACSE的各種服務的參數(如圖2所示),依次為A-ASSOCIATE、A-RELEASE、A-ABORT、A-P-ABORT、P-DATA;第9章給出的是ACSE中各種服務的結構,即STRUCTURE,該部分與【第7部分】中的PROTOCOL相同,給出的是具體ACSE PDU在傳輸時刻的數據格式,該部分也是可以通過抓包工具直接獲得的;同樣對于比較復雜的STRUCTURE介紹也會單獨放到附錄中,例如第8部分的附錄E。
實例工程及抓取的數據包:
后續專欄博文介紹:
利用PHP Skel結合DCMTK開發WEB PACS應用
利用oracle直接操作DICOM數據
C#的異步編程模式在fo-dicom中的應用
VMWare三種網絡連接模式的實際測試
時間:2014-11-18
總結
以上是生活随笔為你收集整理的Java DICOM 网络传输_DICOM医学图像处理:fo-dicom网络传输之 C-Echo and C-Store的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java的character用法_Jav
- 下一篇: java用thinkpadx1_还用老的