最近去醫院部署設備,調試PACS系統,遇到了一個奇葩的問題。基本場景是:醫院內部網絡情況復雜,多個樓層的診室都安裝了看圖端,都需要訪問頂樓機房的PACS服務器。起初為了調試關閉了防火墻,并確保各樓層的看圖端與PACS服務器之間可以ping通,端口也順利開放。但是具體部署調試過程中發現“有些樓層可正常進行worklist查詢和Query/Retrieve查詢,而有些樓層只能正常進行worklist查詢,Query/Retrieve查詢后本地并未獲得圖像數據”;第二天嘗試后發現“原本正常進行worklist和Query/Retrieve查詢的看圖端,只能正常進行worklist查詢,Query/Retrieve查詢后本地無圖像數據,而原本Query/Retrieve查詢失敗的竟然奇跡的可以下載圖像了”。
在看圖端和PACS服務端已經通過ping和talnet指令分別檢測了網絡和端口的連通性,所以說明網絡硬件環境因素基本可以排除。那么問題多半出在DICOM服務端和看圖端配置方面,最糟糕的是系統內部的bug導致(最不希望看到的就是系統的bug,^_^)。首先拷貝PACS服務端與正常看圖端的日志文件,與異常的看圖端日志文件進行對比分析,如下圖所示:
上圖中是正常的看圖端,可以看到在看圖端本地有保存圖像的日志記錄;而下圖中是異常看圖端的日志記錄,對比上圖發現PACS服務端響應看圖端的C-Move請求的消息,即C-Move response,順利到達了看圖端,而看圖端保存圖像數據的信息并未出現。由于發現C-Move response信息能夠順利返回,而圖像卻不能保存所以猜測有可能是醫院網絡環境中對于影像數據的傳輸進行了限制,因為影像傳輸的數據量較大,但是在跟網管溝通后發現網絡方面并未進行任何流量方面的限制。因此第一次排查嘗試失敗。
既然網絡環境沒有限制,那么會是哪一部分出了問題呢?為了對問題有一個更全面的把握,決定對醫院的現有看圖端的情況進行統計,希望能夠從中找到線索,所以開始逐樓層進行排查。首先從最底層樓層開始排查,此時確保其他樓層并未有人使用看圖端。逐個排查后發現有部分看圖端依然只能實現worklist查詢,Query/Retrieve查詢仍然失敗,記錄失敗看圖端的IP地址和AETitle,耗費了一整天的時間統計了多個樓層的看圖端連接情況。經過整理發現大多數Query/Retrieve查詢失敗的看圖端的AETitle竟然有大面積的重合,因此可以斷定大多是由于AETitle的重復而導致的Query/Retrieve查詢失敗。
隨意挑選了AETitle重復的兩臺看圖端,通過修改PACS服務器端和看圖端的AETitle發現問題竟然奇跡般的解決了。于是通過觀察PACS服務端Dicom節點數據庫文件對AETitle出現重復的看圖端進行了修改,順利解決了此次部署中出現的奇葩問題。
現場雖然解決了背景中介紹的奇葩問題,但是并未對問題進行深究,例如既然AETitle有重復,那么為什么所有看圖端worklist查詢都可以成功,唯獨Query/Retrieve查詢會失敗?既然Query/Retrieve查詢失敗,那么為什么PACS服務端的C-Move response響應信息會出現在看圖端的日志文件中?為了對問題進行一個全面的分析,找到問題出現的根源。因此回來后決定復原“現場場景”,希望通過分析本地的源碼找到問題的根源。
為了在同一臺電腦中同時模擬出多臺看圖端與PACS服務端,我們需要借助于VMWare虛擬機工具(最近很火的Docker貌似也可以完成類似的功能,但是由于相關工程是基于Windows開發的,所以估計使用Docker來模擬還有一定的困難,如果有大神曾做過類似的模擬,還請不吝賜教^_^)。
如上圖所示,利用VMWare WorkStation構建兩個虛擬機,模擬現場中出現重復AETitle的看圖端;本地安裝PACSServer模擬醫院的PACS服務器。基本的模擬流程如上圖所示,
1)利用GuestOS-1虛擬機向HostOS上傳一組數據,因為本地HostOS安裝完PACSServer后其中并未存儲相關數據,所以需要利用GuestOS-1上傳來向PACSServer數據庫寫入一條數據;
2)利用GuestOS-1查詢自己上傳的數據,測試環境PACSServer是否正常運行。經測試證明HostOS中的PACSServer運行正常。
隨后利用GuestOS-1和GuestOS-2來復原醫院現場的情況,
3)GuestOS-1看圖端向服務器發起worklist查詢服務;
4)GuestOS-1看圖端順利獲取到PACSServer中的患者信息;
5)GuestOS-2看圖端向服務器發起worklist查詢服務;
6)GuestOS-2看圖端順利獲取到PACSServer中的患者信息;
7)GuestOS-1看圖端向服務器發起Query/Retrieve請求;
8)GuestOS-1看圖端順利獲得C-Move response及圖像信息;
9)GuestOS-2看圖端向服務器發起Query/Retrieve請求;
10)GuestOS-2看圖端順利獲得C-Move response,但是并未獲得圖像信息;
至此成功復原了當時的現場,為本地調試做好了準備。
為了實現上述結構圖中的模擬,需要在GuestOS虛擬機與HostOS之間建立連接。VMWare虛擬機的網絡連接有三種方式:Bridge模式、Host-Only模式、NAT模式。博文(http://www.cnblogs.com/xiaochaohuashengmi/archive/2011/03/15/1985084.html)和博文(http://zhaisx.iteye.com/blog/458671)中對該三種模式有簡單的介紹,大家可以仔細閱讀,此處我選用的是Host-Only模式,HostOS主機的VMware Network Adapter VMnet1的IP地址是192.168.24.1,GuestOS-1的IP地址是192.168.24.100,GuestOS-2的IP地址是192.168.24.200.
此處的DicomViewer看圖端和PACSServer服務端采用的是C#編寫的mDCM開源庫。為了解決上述問題,此處對mDCM開源庫中Dicom Network的相關類進行分析,對mDCM中DICOM網絡服務的實現流程有一個宏觀的認識,期望能夠快速找到上述問題的根源。【注】:專欄的前幾篇博文中提到過mDCM與fo-dicom、DCMTK的關系,有興趣的可以進入我的專欄閱讀一下。
從Github上下載mDCM的源碼,利用VS打開(如下圖)。找到mDCM中關于DICOM網絡服務的文件夾Network,可以看到mDCM按照Client和Server將DICOM網絡服務的實現類分成了兩部分。
上圖中的各個類之間的繼承關系如下所示,
此處通過對PACSServer服務端的實現來剖析一下mDCM的相關類,對于客戶端分支的相關分析此處就不做介紹了,客戶端的流程比服務端要簡單,主要是一個主動連接及被動接收服務端應答的過程,相應的處理函數也比較少,有興趣的同學可自己瀏覽mDCM源碼。
DcmNetworkBase |
DcmNetworkBase是mDCM實現DICOM網路服務的基類,類中給出了基類網絡服務的基礎函數,主要由以下幾類: 1)可重載的各類請求和應答的響應函數,如OnReceiveEchoRequest/OnReceiveEchoResponse、OnReceiveCMoveRequest/OnReceiveCMoveResponse等等。作為基類,各響應函數內部并未真正實現相應的操作,只是簡單的發送終止應答,即調用SendAbort函數。 2)不可重載的,可派生的發送各種請求和應答的函數,如SendEchoRequest/SendEchoResponse、SendCMoveRequest/SendCMoveResponse等等。該類函數按照DICOM網絡服務協議的要求,封裝好了相應的發送操作,派生后可自由使用。 3)私有的,限定關鍵流程的函數,如Process、ProcessNetPDU、ProcessPDataTF、ProcessDimse。這四個函數表明了DICOM服務中信息流的流向,用戶不可修改該流程,但是可通過重載相應的請求和應答函數向該流程中添加自己的操作。 4)私有的,限定底層網絡操作流程的函數,如Connect。用戶可通過重載OnInitializeNetwork函數來向連接流程中添加自己的操作。 |
CStoreService |
CStoreService是服務端用來相應C-STORE 請求的類,即C-STORE-SCP類,派生自DcmNetworkBase基類,并對基類中關于C-STORE請求和C-ECHO請求進行了定制。 重載實現的函數有: 1)OnReceiveAssociateRequest 2)OnReceiveCStoreRequest,函數中調用了OnReceiveCStore委托,通過綁定自己的函數可對CStore請求進行定制化操作。例如數據庫的寫入等。 3)OnReceiveEchoRequest 4)OnReceiveDimseBegin 5)OnReceiveDimseProgress 后兩個函數可以在DcmNetworkBase基類對DIMSE消息進行處理之前添加自己的操作,兩個函數中調用的是OnCStoreRequestBegin和OnCStoreRequestProgress兩個委托,例如可進行相關日志的寫入操作。 |
CEchoService |
該類是一個連接測試類,比較簡單。重載了OnReceiveAssociationRequest和OnReceiveEchoRequest兩個函數。 |
DcmServer<T> where T:DcmSeriveBase |
該類是我們以后編寫PACSServer服務端具體用到了泛型類,該類包含派生自DcmServiceBase的服務類成員,然后通過制定基本的連接流程來實現PACSServer服務端。關于流程的相關函數有, 1)共有的,可訪問的啟動、關閉和端口等相關操作函數,如AddPort、Start、Stop等 2)私有的,不可更改的核心流程控制函數,ServerProc。該函數選用Select模式來接收客戶端的響應,然后針對獲得的客戶端socket對象調用派生自DcmServiceBae的服務類來實現PACSServer的功能,具體PACSServer提供的功能由T:DcmServiceBase類來決定。 |
搜索了網絡上的部分資料,最后從WinPE啟動進入后,對虛擬機的硬盤進行格式化和分區,然后利用WinPE中的安裝工具成功在VMWare虛擬機中安裝Win7操作系統。
Dicom中的MPPS服務介紹
C#的異步編程模式在fo-dicom中的應用
VMWare三種網絡連接模式的實際測試
作者:zssure@163.com
時間:2014-09-28