1. 程式人生 > >DICOM:剖析Orthanc中的Web Server,Mongoose之“連線請求觸發的事件序列”(二)

DICOM:剖析Orthanc中的Web Server,Mongoose之“連線請求觸發的事件序列”(二)

背景:

        Orthanc是本專欄中介紹過的一款新型DICOM伺服器,具有輕量級、支援REST的特性,可將任意執行Windows和Linux系統的計算機變成DICOM伺服器,即miniPACS。Orthanc內嵌多種模組,資料庫管理簡單,且不依賴於第三方軟體。因此通過剖析Orthanc原始碼可以學習到搭建DICOM系統中的各個環節,例如SQLite嵌入型資料庫、GoogleLog日誌庫、DCMTK醫學DICOM庫,以及近期要介紹的開源Web Server,Mongoose。

題記:

        近期計劃參照官網剖析Mongoose原始碼的同時進行例項講解。開源軟體的一大優勢就是原始碼公開,通過剖析原始碼可以學習其優良的設計架構和實現模式,然而對於程式碼上萬行,甚至十幾萬行開源軟體,單純的鑽研原始碼可能會陷入其中而無法自拔。往往在感嘆作者設計巧妙時,迷失了方向;在為各種邏輯求根溯源時,失去了信心;最終在悔恨自己能力有限的同時,放棄了努力。很多良好的架構和模式,其實不一定是設計出來的,至少可以說不是“提前”設計出來的

。任何開源專案都是從無到有,從起步到成熟,一開始就閱讀已成熟的原始碼,享受別人思想果實自然會“無福消受”。要想搞清楚為何設計這種資料結構?為何設計如此執行邏輯?——要從應用場景(context)出發,考慮專案的起源和應用,切記任何設計都是為應用服務的。因此後續博文會採用原始碼分析+例項講解的方式來進行剖析和學習,還是那句話“看原始碼累了就除錯除錯例項,除錯累了就鑽研鑽研原始碼”,勞(原始碼)逸(例項)結合,腳踏實地,Keep Running……

HTTP請求:

        當今網際網路和移動網際網路已經人人皆知,平時我們上網最用的就是HTTP請求,即從客戶端(PC、手機)到伺服器的請求訊息。至於HTTP請求的具體格式就不介紹了,既然是協議就是用來約定雙方互動的規則,讓服務端知道客戶端想做啥,同時讓客戶端理解服務端給了啥。

        HTTP是基於TCP/IP的應用層協議,因此在服務端和客戶端交流資訊之前首先要建立TCP連線。關於連線建立的方式有很多,比如每次請求都建立新的連線、網頁不同部分請求建立不同的連線等等。借用博文Tornado HTTP伺服器的基本流程中的一張HTTP流程圖,大多數HTTP服務端該流程基本相似,Mongoose也一樣,如下圖所示:

Mongoose與Fossa:

        上一篇專欄博文介紹了Mongoose是一個小型的、可嵌入的HTTP Server。只要在自己工程中引入mongoose.h和mongosoe.c原始碼檔案即可快速開啟一個HTTP Server。Mongoose支援REST和JSON-RPC服務(這也是我打算分析Mongoose的根本原因,希望仿照Orthanc將Mongoose嵌入到傳統PACS系統中,對傳統PACS系統進行分散式改造

)。

        開啟Mongoose在Github上的倉庫你會發現Mongoose是建立在NS Skeleton框架之上的,而NS Skeleton者的就是Fossa。官方對Fossa的描述為:Fossa是一個用C語言編寫的支援多協議的網路程式設計庫。採用基於事件驅動的模式,方便使用者實現各種網路協議,開發可擴充套件網路應用。

        截至目前我們可以認為Fossa是底層網路開發庫,Mongoose是對Fossa的一次封裝,強化了事件驅動,方便使用者快速上手,而Orthanc是將Mongoose進行了再一次封裝,強化了REST功能,並提供了與SQLite資料庫和DICOM協議相關的介面。

Mongoose與Fossa的事件:

        如上所述Fossa是基於事件驅動的,Mongoose是對Fossa的封裝,因此同樣採用事件驅動方式。但是兩者的事件略有不同,如下表所示:

Fossa Mongoose

NS_CONNECT

MG_CONNECT

NS_RECV

MG_REQUEST

NS_SEND

MG_POLL

NS_POLL

MG_REPLY

NS_ACCEPT

MG_CLOSE

NS_CLOSE

MG_HTTP_ERROR

        Fossa官網中對於HTTP請求中的事件做了較詳細的介紹,如下表。可能是Mongoose對Fossa封裝變動不大的原因,在Mongoose官網中並未找到關於MG_XXX事件的詳細說明,文件API.md中只是介紹了各個事件的使用方式。

NS_ACCEPT:當監聽埠接收到新的請求連線時,觸發NS_ACCEPT事件
NS_CONNECT:當使用ns_connect建立新的連出連線時,無論新的連線建立失敗還是成功,都觸發NS_CONNECT事件
NS_RECV:當新資料接收完成且寫入到接收緩衝區後,觸發NS_RECV事件
NS_SEND:當資料被寫入遠端節點且從輸出緩衝區清除後,觸發NS_SEND事件
NS_POLL:NS_POLL會被髮送到ns_mgr_poll的連線連結串列中的每個連線,用於進行清理工作(housekeeping),例如檢測是否超時、傳送心電包等等。
NS_CLOSE:連線斷開時觸發。【注】:具體的NS_CLOSE事件何時出發官方文件中並未給出,後續通過多組例項測試再詳細介紹事件觸發條件。

例項講解:

例項測試:

        簡單的理論介紹後,已經大致瞭解了HTTP請求的流程。首先,需要繫結埠(在Windows下就是socket操作),建立TCP連線;然後再根據不同的事件進行相應的處理。         為了更直觀的瞭解Mongoose中HTTP請求的具體流程,我們以安裝說明文件Embbed.md中的例子為基礎,在VS中新增NS_ENDALBE_DEBUG預定義,開啟Mongoose原始碼中原本存在的除錯資訊;另外修改mongoose.c中的mg_ev_handler函式,對Fossa的每一種事件新增除錯語句,輸出當前事件型別。

1)重新生成MongooseEmbbed工程,開啟除錯狀態;


        預設情況下Mongoose與其他Http Server相同,會預設顯示當前目錄下的檔案。並添加了超連結。上圖右側就是MongooseEmbbed工程所在的主目錄下所有檔案的索引,並且已經給出了各個索引的超連結。

Mongoose事件觸發流程除錯:

從控制檯輸出的資訊可以看出啟動初期瀏覽器還未發起任何請求之前,程式處於主函式內的for迴圈中,迴圈輸出除錯資訊;


當瀏覽器位址列中輸入http://localhost:8080後,接下來的for迴圈中,mg_poll_server函式輸出了NS_ACCEPT除錯資訊,說明觸發了NS_ACCEPT事件;


下一個迴圈中,同時輸出了NS_POLL和NS_RECV兩種事件的除錯資訊;


然後同時輸出了NS_POLL、NS_SEND、NS_CLOSE三種事件除錯資訊;


最後進入NS_POLL的輪詢狀態,


        從目前的除錯資訊來看,Mongoose對於瀏覽器發起的連線請求處理的基本流程遵循:NS_ACCEPT->NS_RECV->NS_SEND->NS_POLL->NS_CLOSE。這與Fossa官方說明基本相同,但是在整個流程中會出現多次NS_POLL和NS_ACCEPT,這究竟是什麼原因呢?想要搞清楚整個問題,需要對HTTP Web Server的整體響應流程有詳細的瞭解,需要分析ns_poll_server原始碼中的具體流程。目前該部分思路還未整理完成,打算放到下一篇博文中,敬請關注。