有時(shí),花上幾個(gè)小時(shí)閱讀、調(diào)試、跟蹤的源碼程序,能夠更快地掌握某些技術(shù)關(guān)鍵點(diǎn)和精髓。當(dāng)然,前提是對(duì)這些技術(shù)大致上有一個(gè)了解。
我通過幾個(gè)采用 CSocket 類編寫并基于 Client/Server (客戶端 / 服務(wù)端)的網(wǎng)絡(luò)聊天和傳輸文件的程序 ( 詳見: 源代碼參考 ) ,在調(diào)試這些程序的過程中,追蹤深入至 CSocket 類核心源碼 Sockcore.cpp , 對(duì)于CSocket 類的運(yùn)行機(jī)制可謂是一覽無遺,并且對(duì)于阻塞和非阻塞方式下的 socket 程序的編寫也是稍有體會(huì)。
閱讀本文請(qǐng)先注意:
這里的阻塞和非阻塞的概念僅適用于 Server 端 socket 程序。socket 意為套接字,它與 Socket 不同,請(qǐng)注意首字母的大小寫。
客戶端與服務(wù)端的通信簡單來講:服務(wù)端 socket 負(fù)責(zé)監(jiān)聽,應(yīng)答,接收和發(fā)送消息,而客戶端 socket 只是連接,應(yīng)答,接收,發(fā)送消息。此外,如果你對(duì)于采用 CSocket 類編寫 Client/Server 網(wǎng)絡(luò)程序的原理不是很了解,請(qǐng)先查詢一下( 詳見:參考書籍和在線幫助 )。
在此之前,有必要先講述一下: 網(wǎng)絡(luò)傳輸服務(wù)提供者, ws2_32.dll , socket 事件 和 socket window 。
1、網(wǎng)絡(luò)傳輸服務(wù)提供者(網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程), Socket 事件, Socket Window
網(wǎng)絡(luò)傳輸服務(wù)提供者 ( transport service provider )是以 DLL 的形式存在的,在 windows 操作系統(tǒng)啟動(dòng)時(shí)由服務(wù)進(jìn)程 svchost.exe 加載。當(dāng) socket 被創(chuàng)建時(shí),調(diào)用 API 函數(shù) Socket (在 ws2_32.dll 中), Socket 函數(shù)會(huì)傳遞三個(gè)參數(shù) : 地址族,套接字類型 ( 注 2 ) 和協(xié)議,這三個(gè)參數(shù)決定了是由哪一個(gè)類型的 網(wǎng)絡(luò)傳輸服務(wù)提供者 來啟動(dòng)網(wǎng)絡(luò)傳輸服務(wù)功能。所有的網(wǎng)絡(luò)通信正是由網(wǎng)絡(luò)傳輸服務(wù)提供者完成 , 這里將 網(wǎng)絡(luò)傳輸服務(wù)提供者 稱為 網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 更有助于理解,因?yàn)榍拔囊烟岬?網(wǎng)絡(luò)傳輸服務(wù)提供者 是由 svchost.exe 服務(wù)進(jìn)程所加載的。
下圖描述了網(wǎng)絡(luò)應(yīng)用程序、 CSocket ( WSock32.dll )、 Socket API(ws2_32.dll) 和 網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 之間的接口層次關(guān)系:
當(dāng) Client 端 socket 與 Server 端 socket 相互通信時(shí),兩端均會(huì)觸發(fā) socket 事件。這里僅簡要說明兩個(gè) socket 事件:
FD_CONNECT: 連接事件 , 通常 Client 端 socket 調(diào)用 socket API 函數(shù) Connect 時(shí)所觸發(fā),這個(gè)事件發(fā)生在 Client 端。
FD_ACCEPT :正在引入的連接事件,通常 Server 端 socket 正在接收來自 Client 端 socket 連接時(shí)觸發(fā),這個(gè)事件發(fā)生在 Server 端。
網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 將 socket 事件 保存至 socket 的事件隊(duì)列中。此外, 網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 還會(huì)向 socket window 發(fā)送消息 WM_SOCKET_NOTIFY , 通知有 socket 事件 產(chǎn)生,見下文對(duì) socket window 的詳細(xì)說明。
調(diào)用 CSocket::Create 函數(shù)后,socket 被創(chuàng)建。 socket 創(chuàng)建過程中調(diào)用 CAsyncSocket::AttachHandle(SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead) 。該函數(shù)的作用是: 將 socket 實(shí)例句柄和 socket 指針添加至 當(dāng)前模塊狀態(tài) ( 注 1 )的一個(gè)映射表變量 m_pmapSocketHandle 中。
在 AttachHandle 過程中,會(huì) new 一個(gè) CSocketWnd 實(shí)例 ( 基于 CWnd 派生 ) ,這里將這個(gè)實(shí)例稱之為 socket window ,進(jìn)一步理解為它是存放所有 sockets 的消息池 ( window 消息),請(qǐng)仔細(xì)查看,這里 socket 后多加了一個(gè) s ,表示創(chuàng)建的多個(gè) socket 將共享一個(gè) 消息池 。
當(dāng) Client 端 socket 與 Server 端相互通信時(shí) , 此時(shí) 網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 向 socket window 發(fā)送消息 WM_SOCKET_NOTIFY ,需要說明的是 CSocketWnd 窗口句柄保存在 當(dāng)前模塊狀態(tài) 的 m_hSocketWindow 變量中。
2、阻塞模式
阻塞模式下 Server 端與 Client 端之間的通信處于同步狀態(tài)下。在 Server 端直接實(shí)例化 CSocket 類,調(diào)用 Create 方法創(chuàng)建 socket ,然后調(diào)用方法 Listen 開始偵聽,用一個(gè) while 循環(huán)阻塞調(diào)用 Accept 函數(shù)用于等待來自 Client 端的連接,如果這個(gè) socket 在主線程(主程序)中運(yùn)行,這將導(dǎo)致主線程的阻塞。因此,需要?jiǎng)?chuàng)建一個(gè)新的線程以運(yùn)行 socket 服務(wù)。
調(diào)試跟蹤至 CSocket::Accept 函數(shù)源碼:
while(!Accept(...))
{
// The socket is marked as nonblocking and no connections are present to be accepted.
if (GetLastError() == WSAEWOULDBLOCK) PumpMessage(FD_ACCEPT);
else
return FALSE;
}
它不斷調(diào)用 CAsyncSocket::Accept ( CSocket 派生自 CAsyncSocket 類)判斷 Server 端 socket 的事件隊(duì)列中是否存在正在引入的連接事件 - FD_ACCEPT (見 1 ),換句話說,就是判斷是否有來自 Client 端 socket 的連接請(qǐng)求。
如果當(dāng)前 Server 端 socket 的事件隊(duì)列中存在正在引入的連接事件, Accept 返回一個(gè)非 0 值。否則, Accept 返回 0,此時(shí)調(diào)用 GetLastError 將返回錯(cuò)誤代碼 WSAEWOULDBLOCK ,表示隊(duì)列中無任何連接請(qǐng)求。注意到在循環(huán)體內(nèi)有一句代碼: PumpMessage(FD_ACCEPT);
PumpMessage 作為一個(gè)消息泵使得 socket window 中的消息能夠維持在活動(dòng)狀態(tài)。實(shí)際跟蹤進(jìn)入 PumpMessage 中,發(fā)現(xiàn)這個(gè)消息泵與 Accept 函數(shù)的調(diào)用并不相關(guān),它只是使很少的 socket window 消息(典型的是 WM_PAINT 窗口重繪消息)處于活動(dòng)狀態(tài),而絕大部分的 socket window 消息被阻塞,被阻塞的消息中含有 WM_SOCKET_NOTIFY。
很顯然,如果沒有來自 Client 端 socket 的連接請(qǐng)求, CSocket 就會(huì)不斷調(diào)用 Accept 產(chǎn)生循環(huán)阻塞,直到有來自 Client 端 socket 的連接請(qǐng)求而解除阻塞。
阻塞解除后,表示 Server 端 socket 和 Client 端 socket 已成功連接, Server 端與 Client 端彼此相互調(diào)用 Send 和 Receive 方法開始通信。
3、非阻塞模式
在非阻塞模式下 利用 socket 事件 的消息機(jī)制, Server 端與 Client 端之間的通信處于異步狀態(tài)下。
通常需要從 CSocket 類派生一個(gè)新類,派生新類的目的是重載 socket 事件 的消息函數(shù),然后在 socket 事件 的消息函數(shù)中添入合適的代碼以完成 Client 端與 Server 端之間的通信,與阻塞模式相比,非阻塞模式無需創(chuàng)建一個(gè)新線程。
這里將討論當(dāng) Server 端 socket 事件 - FD_ACCEPT 被觸發(fā)后,該事件的處理函數(shù) OnAccept 是如何進(jìn)一步被觸發(fā)的。其它事件的處理函數(shù)如 OnConnect, OnReceive 等的觸發(fā)方式與此類似。
在 1 中已提到 Client/Server 端通信時(shí), Server 端 socket 正在接收來自 Client 端 socket 連接請(qǐng)求,這將會(huì)觸發(fā) FD_ACCEPT 事件,同時(shí) Server 端的 網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 向 Server 端的 socket window (CSocketWnd )發(fā)送事件通知消息 WM_SOCKET_NOTIFY , 通知有 FD_ACCEPT 事件產(chǎn)生 , CsocketWnd 在收到事件通知消息后,調(diào)用消息處理函數(shù) OnSocketNotify:
LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam)
{
CSocket::AuxQueueAdd(WM_SOCKET_NOTIFY, wParam, lParam);
CSocket::ProcessAuxQueue();
return 0L ;
}
消息參數(shù) wParam 是 socket 的句柄, lParam 是 socket 事件 。這里稍作解釋一下,CSocketWnd 類是作為 CSocket 類的 友元類 ,這意味著它可以訪問 CSocket 類中的保護(hù)和私有成員函數(shù)和變量, AuxQueueAdd 和 ProcessAuxQueue 是 CSocket 類的靜態(tài)成員函數(shù),如果你對(duì)友元不熟悉,請(qǐng)迅速找本有關(guān) C++ 書看一下友元的使用方法吧!
ProcessAuxQueue 是實(shí)質(zhì)處理 socket 事件的函數(shù),在該函數(shù)中有這樣一句代碼: CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE);
其實(shí)也就是由 socket 句柄得到發(fā)送事件通知消息的 socket 指針 pSocket:從 m_pmapSocketHandle 中查找!
, WSAGETSELECTEVENT(lParam) 會(huì)取出事件類型,在一個(gè)簡單的 switch 語句中判斷事件類型并調(diào)用事件處理函數(shù)。在這里,事件類型是 FD_ACCEPT ,當(dāng)然就調(diào)用 pSocket->OnAccept !
結(jié)束語
Server 端 socket 處于阻塞調(diào)用模式下,它必須在一個(gè)新創(chuàng)建的線程中工作,防止主線程被阻塞。
當(dāng)有多個(gè) Client 端 socket 與 Server 端 socket 連接及通信時(shí), Server 端采用阻塞模式就顯得不適合了,應(yīng)該采用非阻塞模式 , 利用 socket 事件 的消息機(jī)制來接受多個(gè) Client 端 socket 的連接請(qǐng)求并進(jìn)行通信。
在非阻塞模式下,利用 CSocketWnd 作為所有 sockets 的消息池,是實(shí)現(xiàn) socket 事件 的消息機(jī)制的關(guān)鍵技術(shù)。文中存在用詞不妥和可能存在的技術(shù)問題,請(qǐng)大家原諒,也請(qǐng)批評(píng)指正,謝謝!
注:
當(dāng)前模塊狀態(tài)——用于保存當(dāng)前線程和模塊狀態(tài)的一個(gè)結(jié)構(gòu),可以通過 AfxGetThreadModule() 獲得。AFX_MODULE_THREAD_STATE 在 CSocket 重新定義為 _AFX_SOCK_THREAD_STATE 。
socket 類型——在 TCP/IP 協(xié)議中, Client/Server 網(wǎng)絡(luò)程序采用 TCP 協(xié)議:即 socket 類型為 SOCK_STREAM ,它是可靠的連接方式。在這里不采用 UDP 協(xié)議:即 socket 類型為 SOCK_DGRAM ,它是不可靠的連接方式。
深入 CSocket 編程之阻塞和非阻塞模式
更新時(shí)間: 2007-05-09 09:35:38來源: 粵嵌教育瀏覽量:1193
粵嵌動(dòng)態(tài)
推薦閱讀
- ·Linux字符設(shè)備驅(qū)動(dòng)框架解析:file_operations的核心作用與實(shí)現(xiàn)
- ·廣東朝歌數(shù)碼科技股份有限公司專場招聘會(huì)
- ·深化產(chǎn)教融合,共筑技能人才培養(yǎng)新生態(tài) —— 廣州華立學(xué)院到訪粵嵌從化校區(qū)為深化產(chǎn)教
- ·校企合作新突破 | 粵嵌科技與三亞學(xué)院共探產(chǎn)教融合新路徑
- ·粵嵌科技入選國家級(jí)職業(yè)數(shù)字展館聯(lián)合建設(shè)單位,賦能計(jì)算機(jī)程序設(shè)計(jì)員高技能人才培養(yǎng)
- ·嵌入式實(shí)時(shí)操作系統(tǒng)的性能優(yōu)化與實(shí)現(xiàn)路徑
- ·校企攜手賦能教育!粵嵌科技助力海南科技職業(yè)大學(xué)探索 AGI 時(shí)代教學(xué)新范式
- ·嵌入式系統(tǒng)中的低功耗設(shè)計(jì)策略與實(shí)現(xiàn)路徑
- ·深圳市軒宇軟件開發(fā)有限公司專場招聘會(huì)
- ·嵌入式系統(tǒng)中的代碼空間優(yōu)化:策略與實(shí)踐