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

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

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

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

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

4、elect )模型select 模型是一個廣泛在Winsock 中使用的I/O 模型。稱它為select 模型,是因為它主要是使用select 函數(shù)來管理I/O 的。這個模式的設(shè)計源于UNIX 系統(tǒng),目的是允許那些想要避免在套接字調(diào)用上阻塞的應(yīng)用程序有能力管理多個套接字。53.2.1 select 函數(shù)select 函數(shù)可以確定一個或者多個套接字的狀態(tài)。如果套接字上沒有網(wǎng)絡(luò)事件發(fā)生,便進(jìn)入等待狀態(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ù)調(diào)用成功,返回發(fā)生網(wǎng)絡(luò)事件的所有套接字?jǐn)?shù)量的總和。如果超過了時間限制,返回0,失敗則返回SOCKET_ERROR。1套接字集合fd_set 結(jié)構(gòu)可以把多個套接字連在一起,形成一個套接字集合。select 函數(shù)可以測試這個集合中哪些套接字有事件發(fā)生。下面是這個結(jié)構(gòu)在WINSOCK2.h 中的定義。63.2.1

6、select 函數(shù)1套接字集合fd_set 結(jié)構(gòu)可以把多個套接字連在一起,形成一個套接字集合。select 函數(shù)可以測試這個集合中哪些套接字有事件發(fā)生。下面是這個結(jié)構(gòu)在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 為空集合。集合在使用前應(yīng)該總是清空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ù)網(wǎng)絡(luò)事件傳遞給select 函數(shù)的3 個fd_set 結(jié)構(gòu)中,一個是為了檢查可讀性(readfds),一個是為了檢查可寫性(writefds),另一個是為了檢查錯誤(exceptfds)。 select 函數(shù)返回之后,如果有下列事件發(fā)生,其對應(yīng)的套接字就會被標(biāo)識。 93.2.1 select 函數(shù)下面的例子示例了select 函數(shù)的用法。程序運行之后,在4567 端口監(jiān)聽,接受客戶端連接請求,打印出接

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

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

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

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

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

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

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

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

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

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

18、ts / 感興趣的FD_XXX 網(wǎng)絡(luò)事件的組合); 網(wǎng)絡(luò)事件與事件對象關(guān)聯(lián)之后,應(yīng)用程序便可以在事件對象上等待了。Winsock 提供了WSAWaitForMultipleEvents 函數(shù)在一個或多個事件對象上等待,當(dāng)所等待的事件對象受信,或者指定的時間過去時,此函數(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 模型時可以忽略,應(yīng)設(shè)為FALSE ); WSAWaitForMultipleEvents 最多支持WSA_MAXIMUM_WAIT_EVENTS 個對象,WSA_MAXIMUM_WAIT_EVENTS 被定義為64。因此,這個I/O 模型在一個線程中同一時間最多能支持64 個套接字,如果需要使用這個模型管理更多套接字,就需要創(chuàng)建額外的

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

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

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

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

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

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

26、in addrRemote; / 客戶端地址信息_SOCKET_OBJ *pNext; / 指向下一個SOCKET_OBJ 對象,以連成一個表 SOCKET_OBJ, *PSOCKET_OBJ; 服務(wù)器程序每接受到一個新的連接,便為新連接申請一個SOCKET_OBJ 結(jié)構(gòu),初始化該結(jié)構(gòu)的成員。當(dāng)連接關(guān)閉或者出錯時,再釋放內(nèi)存空間。下面的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)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論