1. gzyueqian
      18529173453
      首頁 > 新聞中心 > > 正文

      深入 CSocket 編程之阻塞和非阻塞模式

      更新時(shí)間: 2007-05-09 09:35:38來源: 粵嵌教育瀏覽量:1193


        有時(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 ,它是不可靠的連接方式。

      免費(fèi)預(yù)約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 日韩欧美a∨中文字幕 | 日韩中文字幕在线视频 | 日韩精品一区二区三区视频免费看 | 久久精品国产首页国产 | 日韩一区二区三区在线观看视频 | 日韩AV在线中文字幕高清 |