網絡通信編程技術3_第1頁
網絡通信編程技術3_第2頁
網絡通信編程技術3_第3頁
網絡通信編程技術3_第4頁
網絡通信編程技術3_第5頁
已閱讀5頁,還剩23頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、第3 章 Windows 套接字I/O 模型 1阻塞(blocking)模型選擇(select)模型WSAAsyncSelect 模型WSAEventSelect 模型重疊(overlapped )模型完成端口(completion port )模型23.1.1 阻塞模式套接字創(chuàng)建時,默認工作在阻塞模式下。例如,對recv 函數(shù)的調用會使程序進入等待狀態(tài),直到接收到數(shù)據才返回。第2 章的示例程序都是這種情況。大多數(shù)Winsock 程序設計者都是從阻塞套接字模式開始學習的,因為這是最容易和最直接的方式。處理阻塞模式套接字的應用程序使用的程序框架便是阻塞模型。此模型是非常容易理解的。阻塞套接字的好

2、處是使用簡單,但是當需要處理多個套接字連接時,就必須創(chuàng)建多個線程,即典型的一個連接使用一個線程的問題,這給編程帶來了許多不便。所以實際開發(fā)中使用最多的還是下面要講述的非阻塞模式。33.1.2 非阻塞模式非阻塞套接字使用起來比較復雜,但是卻有許多優(yōu)點。應用程序可以調用ioctlsocket 函數(shù)顯式地讓套接字工作在非阻塞模式下,如下代碼所示。u_long ul = 1; SOCKET s = socket(AF_INET, SOCK_STREAM, 0); ioctlsocket(s, FIONBIO, (u_long *)&ul); 一旦套接字被置于非阻塞模式,處理發(fā)送和接收數(shù)據或者管理連接的

3、Winsock 調用將會立即返回。大多少情況下,調用失敗的出錯代碼是WSAEWOULDBLOCK ,這意味著請求的操作在調用期間沒有完成。例如,如果系統(tǒng)輸入緩沖區(qū)中沒有待處理的數(shù)據,那么對recv 的調用將返回WSAEWOULDBLOCK 。通常,要對相同函數(shù)調用多次,直到它返回成功為止。非阻塞調用經常以WSAEWOULDBLOCK 出錯代碼失敗,所以將套接字設置為非阻塞之后,關鍵的問題在于如何確定套接字什么時候可讀/可寫,也就是說確定網絡事件何時發(fā)生。如果需要自己不斷調用函數(shù)去測試的話,程序的性能勢必會受到影響,解決的辦法就是使用Windows 提供的不同的I/O 模型, 43.2 選擇(s

4、elect )模型select 模型是一個廣泛在Winsock 中使用的I/O 模型。稱它為select 模型,是因為它主要是使用select 函數(shù)來管理I/O 的。這個模式的設計源于UNIX 系統(tǒng),目的是允許那些想要避免在套接字調用上阻塞的應用程序有能力管理多個套接字。53.2.1 select 函數(shù)select 函數(shù)可以確定一個或者多個套接字的狀態(tài)。如果套接字上沒有網絡事件發(fā)生,便進入等待狀態(tài),以便執(zhí)行同步I/O。函數(shù)定義如下。int select( int nfds, / 忽略,僅是為了與Berkeley 套接字兼容fd_set* readfds, / 指向一個套接字集合,用來檢查其可讀

5、性 fd_set* writefds, / 指向一個套接字集合,用來檢查其可寫性 fd_set* exceptfds, / 指向一個套接字集合,用來檢查錯誤const struct timeval* timeout / 指定此函數(shù)等待的最長時間,如果為NULL,則最長時間為無限大); 函數(shù)調用成功,返回發(fā)生網絡事件的所有套接字數(shù)量的總和。如果超過了時間限制,返回0,失敗則返回SOCKET_ERROR。1套接字集合fd_set 結構可以把多個套接字連在一起,形成一個套接字集合。select 函數(shù)可以測試這個集合中哪些套接字有事件發(fā)生。下面是這個結構在WINSOCK2.h 中的定義。63.2.1

6、select 函數(shù)1套接字集合fd_set 結構可以把多個套接字連在一起,形成一個套接字集合。select 函數(shù)可以測試這個集合中哪些套接字有事件發(fā)生。下面是這個結構在WINSOCK2.h 中的定義。typedef struct fd_set u_int fd_count; / 下面數(shù)組的大小 SOCKET fd_arrayFD_SETSIZE; / 套接字句柄數(shù)組 fd_set; 下面是WINSOCK 定義的4 個操作fd_set 套接字集合的宏。73.2.1 select 函數(shù)FD_ZERO(*set) 初始化set 為空集合。集合在使用前應該總是清空z FD_CLR(s, *set) 從

7、set 移除套接字s z FD_ISSET(s, *set) 檢查s 是不是set 的成員,如果是返回TRUE z FD_SET(s, *set) 添加套接字到集合83.2.1 select 函數(shù)網絡事件傳遞給select 函數(shù)的3 個fd_set 結構中,一個是為了檢查可讀性(readfds),一個是為了檢查可寫性(writefds),另一個是為了檢查錯誤(exceptfds)。 select 函數(shù)返回之后,如果有下列事件發(fā)生,其對應的套接字就會被標識。 93.2.1 select 函數(shù)下面的例子示例了select 函數(shù)的用法。程序運行之后,在4567 端口監(jiān)聽,接受客戶端連接請求,打印出接

8、收到的數(shù)據。大家可以看到采用select 模型之后,即便是在單個線程中,也可以管理多個套接字。具體編程流程如下:(1)初始化套接字集合fdSocket,向這個集合添加監(jiān)聽套接字句柄。(2)將fdSocket 集合的拷貝fdRead 傳遞給select 函數(shù),當有事件發(fā)生時,select 函數(shù)移除fdRead 集合中沒有未決I/O 操作的套接字句柄,然后返回。(3)比較原來fdSocket 集合與select 處理過的fdRead 集合,確定哪些套接字有未決I/O, 并進一步處理這些I/O。(4)回到第2 步繼續(xù)進行選擇處理。 103.2.1 select 函數(shù)實際例程113.3 WSAAsyn

9、cSelect 模型WSAAsyncSelect 模型允許應用程序以Windows 消息的形式接收網絡事件通知。這個模型是為了適應Windows 的消息驅動環(huán)境而設置的,現(xiàn)在許多對性能要求不高的網絡應用程序都采用WSAAsyncSelect 模型,MFC(Microsoft Foundation Class,Microsoft 基礎類庫)中的CSocket 類也使用了它。123.3 WSAAsyncSelect 模型3.3.1 消息通知和WSAAsyncSelect 函數(shù)WSAAsyncSelect 函數(shù)自動把套接字設為非阻塞模式,并且為套接字綁定一個窗口句柄,當有網絡事件發(fā)生時,便向這個窗口

10、發(fā)送消息。函數(shù)用法如下。int WSAAsyncSelect( SOCKET s, / 需要設置的套接字句柄 HWND hWnd, / 指定一個窗口句柄, / 套接字的通知消息將被發(fā)送到與其對應的窗口過程中 u_int wMsg, / 網絡事件到來時接收到的消息ID,/ 可以在WM_USER 以上的數(shù)值中任意選擇一個用做ID。 long lEvent / 指定哪些通知碼需要發(fā)送 ); 133.3 WSAAsyncSelect 模型最后一個參數(shù)lEvent 指定了要發(fā)送的通知碼,可以是如下取值的組合:z FD_READ 套接字接收到對方發(fā)送過來的數(shù)據包,表明這時可以去讀套接字了z FD_WRIT

11、E 數(shù)據緩沖區(qū)滿后再次變空時,WinSock 接口通過該通知碼通知應用程序。表示可以繼續(xù)發(fā)送數(shù)據了(短時間內發(fā)送數(shù)據過多,便會造成數(shù)據緩沖區(qū)變滿)z FD_ACCEPT 監(jiān)聽中的套接字檢測到有連接進入z FD_CONNECT 如果用套接字去連接對方的主機,當連接動作完成以后會接收到這個通知碼z FD_CLOSE 檢測到套接字對應的連接被關閉例如,在監(jiān)聽套接字上可以這樣調用WSAAsyncSelect 函數(shù)::WSAAsyncSelect(sListen, hWnd, WM_SOCKET, FD_ACCEPT|FD_CLOSE); / WM_SOCKET 為自定義消息上述代碼將套接字sListe

12、n 設為窗口通知消息類型。WM_SOCKET 為自定義網絡通知消息,F(xiàn)D_CLOSE|FD_ACCEPT 指定了sListen 套接字只接收FD_CLOSE 和FD_ACCEPT 通知消息。當有客戶連接或套接字關閉時,Winsock 接口將向指定的窗口發(fā)送WM_SOCKET 消息。143.3 WSAAsyncSelect 模型最后一個參數(shù)lEvent 指定了要發(fā)送的通知碼,可以是如下取值的組合:z FD_READ 套接字接收到對方發(fā)送過來的數(shù)據包,表明這時可以去讀套接字了z FD_WRITE 數(shù)據緩沖區(qū)滿后再次變空時,WinSock 接口通過該通知碼通知應用程序。表示可以繼續(xù)發(fā)送數(shù)據了(短時間

13、內發(fā)送數(shù)據過多,便會造成數(shù)據緩沖區(qū)變滿)z FD_ACCEPT 監(jiān)聽中的套接字檢測到有連接進入z FD_CONNECT 如果用套接字去連接對方的主機,當連接動作完成以后會接收到這個通知碼z FD_CLOSE 檢測到套接字對應的連接被關閉例如,在監(jiān)聽套接字上可以這樣調用WSAAsyncSelect 函數(shù)::WSAAsyncSelect(sListen, hWnd, WM_SOCKET, FD_ACCEPT|FD_CLOSE); / WM_SOCKET 為自定義消息上述代碼將套接字sListen 設為窗口通知消息類型。WM_SOCKET 為自定義網絡通知消息,F(xiàn)D_CLOSE|FD_ACCEPT

14、指定了sListen 套接字只接收FD_CLOSE 和FD_ACCEPT 通知消息。當有客戶連接或套接字關閉時,Winsock 接口將向指定的窗口發(fā)送WM_SOCKET 消息。153.3 WSAAsyncSelect 模型成功調用WSAAsyncSelect 之后,應用程序便開始以Windows 消息的形式在窗口函數(shù)接收網絡事件通知。下面是窗口函數(shù)的定義。LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); wParam 參數(shù)指定了發(fā)生網絡事件的套接字句柄,lParam 參數(shù)的低字位指定

15、了發(fā)生的網絡事件,高字位包含了任何可能出現(xiàn)的錯誤代碼,可以使用宏WSAGETSELECTERROR 和WSAGETSELECTEVENT 將這些信息取出,這兩個宏定義在Winsock2.h 文件中。#define WSAGETSELECTERROR(lParam) HIWORD(lParam) / 高字為出錯代碼#define WSAGETSELECTEVENT(lParam) LOWORD(lParam) / 低字為通知碼如果沒有錯誤發(fā)生,出錯代碼為0,程序可以繼續(xù)檢查通知碼,以確定發(fā)生的網絡事件。163.3 WSAAsyncSelect 模型3.3.2 應用舉例。173.4 WSAEven

16、tSelect 模型Winsock 提供了另一種有用的異步事件通知I/O 模型WSAEventSelect 模型。這個模型與WSAAsyncSelect 模型類似,允許應用程序在一個或者多個套接字上接收基于事件的網絡通知。它與WSAAsyncSelect 模型類似是因為它也接收FD_XXX 類型的網絡事件,不過并不是依靠Windows 的消息驅動機制,而是經由事件對象句柄通知。183.4.1 WSAEventSelect 函數(shù)使用這個模型的基本思路是為感興趣的一組網絡事件創(chuàng)建一個事件對象,再調用WSAEventSelect 函數(shù)將網絡事件和事件對象關聯(lián)起來。當網絡事件發(fā)生時,Winsock 使

17、相應的事件對象受信,在事件對象上的等待函數(shù)就會返回。之后,調用WSAEnumNetworkEvents 函數(shù)便可獲取到底發(fā)生了什么網絡事件。Winsock 中創(chuàng)建事件對象的函數(shù)是WSACreateEvent ,定義如下:WSAEVENT WSACreateEvent(void); / 返回一個手工重置的事件對象句柄創(chuàng)建事件對象之后,必須調用WSAEventSelect 函數(shù)將指定的一組網絡事件與它關聯(lián)在一起,函數(shù)用法如下。int WSAEventSelect( SOCKET s, / 套接字句柄 WSAEVENT hEventObject, / 事件對象句柄 long lNetworkEven

18、ts / 感興趣的FD_XXX 網絡事件的組合); 網絡事件與事件對象關聯(lián)之后,應用程序便可以在事件對象上等待了。Winsock 提供了WSAWaitForMultipleEvents 函數(shù)在一個或多個事件對象上等待,當所等待的事件對象受信,或者指定的時間過去時,此函數(shù)返回。WSAWaitForMultipleEvents 函數(shù)用法如下。DWORD WSAWaitForMultipleEvents( DWORD cEvents, / 指定下面lphEvents 所指的數(shù)組中事件對象句柄的個數(shù) const WSAEVENT* lphEvents, / 指向一個事件對象句柄數(shù)組BOOL fWait

19、All, / 指定是否等待所有事件對象都變成受信狀態(tài)193.4.1 WSAEventSelect 函數(shù)DWORD dwTimeout, / 指定要等待的時間,WSA_INFINITE 為無窮大BOOL fAlertable / 在使用WSAEventSelect 模型時可以忽略,應設為FALSE ); WSAWaitForMultipleEvents 最多支持WSA_MAXIMUM_WAIT_EVENTS 個對象,WSA_MAXIMUM_WAIT_EVENTS 被定義為64。因此,這個I/O 模型在一個線程中同一時間最多能支持64 個套接字,如果需要使用這個模型管理更多套接字,就需要創(chuàng)建額外的

20、工作線程了。WSAWaitForMultipleEvents 函數(shù)會等待網絡事件的發(fā)生。如果過了指定的時間,函數(shù)返回WSA_WAIT_TIMEOUT ;如果在指定時間內有網絡事件發(fā)生,函數(shù)的返回值會指明是哪一個事件對象促使函數(shù)返回的;函數(shù)調用失敗時返回值是WSA_WAIT_FAILED 。也可以將dwTimeout 的值設為0,這時函數(shù)測試指定事件對象的狀態(tài),并立即返回,通過函數(shù)的返回值便可知道事件對象是否受信。注意,將fWaitAll 參數(shù)設為FALSE 以后,如果同時有幾個事件對象受信,WSAWaitForMultipleEvents 函數(shù)的返回值也僅能指明一個,就是句柄數(shù)組中最前面的那個

21、。如果指明的這個事件對象總有網絡時間發(fā)生,那么后面其他事件對象所關聯(lián)的網絡事件就得不到處理了。解決辦法是,WSAWaitForMultipleEvents 函數(shù)返回后,對每個事件都再次調用WSAWaitForMultipleEvents 函數(shù),以便確定其狀態(tài)。具體過程請參考后面的實例代碼。 203.4.1 WSAEventSelect 函數(shù)一旦事件對象受信,那么找到與之對應的套接字,然后調用WSAEnumNetworkEvents 函數(shù)即可查看發(fā)生了什么網絡事件,函數(shù)用法如下。int WSAEnumNetworkEvents( SOCKET s, / 套接字句柄 WSAEVENT hEvent

22、Object, / 對應的事件對象句柄。如果提供了此參數(shù),本函數(shù)會重置這個事件對象的狀態(tài) LPWSANETWORKEVENTS lpNetworkEvents / 指向一個WSANETWORKEVENTS 結構); 最后一個參數(shù)用來取得在套接字上發(fā)生的網絡事件和相關的出錯代碼,其結構定義如下。typedef struct _WSANETWORKEVENTS long lNetworkEvents; / 指定已發(fā)生的網絡事件(如FD_ACCEPT 、FD_READ 等) int iErrorCodeFD_MAX_EVENTS; / 與lNetworkEvents 相關的出錯代碼 WSANETWO

23、RKEVENTS, *LPWSANETWORKEVENTS; iErrorCode 參數(shù)是一個數(shù)組,數(shù)組的每個成員對應著一個網絡事件的出錯代碼。可以用預定義標識FD_READ_BIT 、FD_WRITE_BIT 等來索引FD_READ、FD_WRITE 等事件發(fā)生時的出錯代碼。如下面代碼片段所示。if(event.lNetworkEvents & FD_READ) / 處理FD_READ 通知消息213.4.2 應用舉例下面使用WSAEventSelect 模型重寫上節(jié)的TCP 服務器例子。使用WSAEventSelect 模型編程的基本步驟如下。(1)創(chuàng)建一個事件句柄表和一個對應的套接字句柄

24、表。(2)每創(chuàng)建一個套接字,就創(chuàng)建一個事件對象,把它們的句柄分別放入上面的兩個表中,并調用WSAEventSelect 添加它們的關聯(lián)。(3)調用WSAWaitForMultipleEvents 在所有事件對象上等待,此函數(shù)返回后,我們對事件句柄表中的每個事件調用WSAWaitForMultipleEvents 函數(shù),以便確認在哪些套接字上發(fā)生了網絡事件。(4)處理發(fā)生的網絡事件,繼續(xù)在事件對象上等待。下面是程序代碼。223.4.3 基于WSAEventSelect 模型的服務器設計這個例子的功能和上一小節(jié)的一樣,不同的是它使用了線程池,可以處理大量的客戶I/O 請求。這個例子稍微復雜,但卻是

25、后面設計功能更強大的服務器程序的基礎。設計的總體思路比較簡單,程序的主線程負責監(jiān)聽客戶端的連接請求,接受到新連接之后,將新套接字安排給工作線程處理I/O。每個工作線程最多處理64 個套接字,如果再有新的套接字,就再創(chuàng)建新的工作線程。下面先討論兩個重要的結構,然后再講述具體的實現(xiàn)代碼。1套接字對象程序用下面的SOCKET_OBJ 結構來記錄每個客戶端套接字的信息。typedef struct _SOCKET_OBJ 233.4.3 基于WSAEventSelect 模型的服務器設計 SOCKET s; / 套接字句柄HANDLE event; / 與此套接字相關聯(lián)的事件對象句柄sockaddr_

26、in addrRemote; / 客戶端地址信息_SOCKET_OBJ *pNext; / 指向下一個SOCKET_OBJ 對象,以連成一個表 SOCKET_OBJ, *PSOCKET_OBJ; 服務器程序每接受到一個新的連接,便為新連接申請一個SOCKET_OBJ 結構,初始化該結構的成員。當連接關閉或者出錯時,再釋放內存空間。下面的GetSocketObj 和FreeSocketObj 函數(shù)分別用于申請和釋放一個SOCKET_OBJ 對象。PSOCKET_OBJ GetSocketObj(SOCKET s) / 申請一個套接字對象,初始化它的成員 PSOCKET_OBJ pSocket = (PSOCKET_OBJ):GlobalAlloc(GPTR, sizeof(SOCKET_OBJ); if(pSocket != NULL) pSocket-s = s; pSocket-event = :WSACreateEvent(); return pSocket; void FreeSocketObj(PSOCKET_OBJ pSocket) / 釋放一個套接字對象 :CloseHandle(pSocket-event); if(pSocke

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論