Windows網(wǎng)絡(luò)編程實(shí)用教程:第8章 高級Socket編程_第1頁
Windows網(wǎng)絡(luò)編程實(shí)用教程:第8章 高級Socket編程_第2頁
Windows網(wǎng)絡(luò)編程實(shí)用教程:第8章 高級Socket編程_第3頁
Windows網(wǎng)絡(luò)編程實(shí)用教程:第8章 高級Socket編程_第4頁
Windows網(wǎng)絡(luò)編程實(shí)用教程:第8章 高級Socket編程_第5頁
已閱讀5頁,還剩111頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡介

1、第8章 高級Socket編程課程描述我們在第5章中介紹了Socket編程的基礎(chǔ)技術(shù),可以實(shí)現(xiàn)簡單的服務(wù)器和客戶機(jī)通信。但在實(shí)際應(yīng)用中,服務(wù)器往往需要同時與很多客戶端進(jìn)行通信,對服務(wù)器的性能要求很高。要在Windows平臺上構(gòu)建高效、實(shí)用的客戶機(jī)/服務(wù)器應(yīng)用程序,就必須選擇最適合的Socket編程模型。 本章知識點(diǎn)Socket編程模型概述阻塞與非阻塞模式Socket編程基于Select模型的Socket編程基于WSAAsyncSelect模型的Socket編程基于WSAEventSelect模型的Socket編程基于重疊I/O模型的Socket編程基于完成端口模型的Socket編程8.1 Soc

2、ket編程模型概述Socket編程可以分為阻塞和非阻塞兩種開發(fā)模式。阻塞模式是指在指定套接字上調(diào)用函數(shù)執(zhí)行操作時,在沒有完成操作之前,函數(shù)不會立即返回。例如,服務(wù)器程序在阻塞模式下調(diào)用accept()函數(shù)時將會阻塞服務(wù)器線程,直至接收到一個來自客戶端的連接請求。默認(rèn)創(chuàng)建的套接字為阻塞模式。非阻塞模式是指在指定套接字上調(diào)用函數(shù)執(zhí)行操作時,無論操作是否完成,函數(shù)都會立即返回。例如,在非阻塞模式下調(diào)用recv()函數(shù)時,程序會直接讀取網(wǎng)絡(luò)緩沖區(qū)中的數(shù)據(jù),無論是否讀到數(shù)據(jù),函數(shù)都會立即返回,而不會一直掛在此函數(shù)的調(diào)用上。1Select模型Select模型又稱為選擇模型,它可以使Windows Sock

3、ets應(yīng)用程序同時對多個套接字進(jìn)行管理,調(diào)用select()函數(shù)可以獲取指定套接字的狀態(tài)。然后調(diào)用Windows Sockets API實(shí)現(xiàn)數(shù)據(jù)發(fā)送和接收等操作。select()函數(shù)中使用集合來表示進(jìn)行管理的多個套接字。默認(rèn)情況下,套接字集合中包含64個元素,最多可以管理的套接字?jǐn)?shù)量為1024個。盡管Select模型可以同時管理多個連接,但對集合的管理比較繁瑣。而且每次在使用套接字發(fā)送和接收數(shù)據(jù)之前,都需要調(diào)用select()函數(shù)判斷套接字的狀態(tài),這會導(dǎo)致CPU額外的負(fù)擔(dān),從而影響應(yīng)用程序的工作效率。 2WSAAsyncSelect模型WSAAsyncSelect模型又稱為異步選擇模型,它為每

4、個套接字綁定一個消息。當(dāng)套接字上出現(xiàn)事先設(shè)置事件時,操作系統(tǒng)會給應(yīng)用程序發(fā)送這個消息,從而使應(yīng)用程序可以對該事件做相應(yīng)的處理。WSAAsyncSelect模型的優(yōu)點(diǎn)是在系統(tǒng)開銷不大的情況下可以同時處理許多個客戶端連接。它的缺點(diǎn)是,即使應(yīng)用程序不需要窗口,也要至少設(shè)計一個窗口用于處理套接字事件。而且,在一個窗口中處理大量的事件也可能成為性能瓶頸。 3WSAEventSelect模型WSAEventSelect模型又稱為事件Select模型,它允許在多個套接字上接收以事件為基礎(chǔ)的網(wǎng)絡(luò)事件通知。應(yīng)用程序在創(chuàng)建套接字后,調(diào)用WSAEventSelect()函數(shù)將事件對象與網(wǎng)絡(luò)事件集合相關(guān)聯(lián)。當(dāng)網(wǎng)絡(luò)事件

5、發(fā)生時,應(yīng)用程序以事件的形式接收網(wǎng)絡(luò)事件通知。WSAEventSelect模型與WSAAsyncSelect模型之間的主要區(qū)別是網(wǎng)絡(luò)事件發(fā)生時系統(tǒng)通知應(yīng)用程序的方式不同。WSAAsyncSelect模型以消息的形式通知應(yīng)用程序,而WSAEventSelect模型則以事件的形式進(jìn)行通知。Select模型是主動獲取指定套接字的狀態(tài),而WSAEventSelect模型和WSAAsyncSelect模型則是被動選擇系統(tǒng)通知應(yīng)用程序套接字的狀態(tài)變化。WSAEventSelect模型每次只能等待64個事件,這也是WSAEventSelect模型的不足之處。4重疊I/O模型重疊I/O模型又稱為Overlap

6、ped I/O模型,它的基本設(shè)計原理是可以讓應(yīng)用程序使用重疊的數(shù)據(jù)結(jié)構(gòu)一次投遞多個I/O請求,當(dāng)系統(tǒng)完成I/O操作后通知應(yīng)用程序。重疊I/O模型是真正意義上的異步I/O模型。在應(yīng)用程序中調(diào)用輸入/輸出函數(shù)后,程序?qū)⒘⒓捶祷?。?dāng)I/O操作完成后,系統(tǒng)會通知應(yīng)用程序。系統(tǒng)通知應(yīng)用程序的形式有兩種,即事件通知和完成例程。事件通知方式即通過事件來通知應(yīng)用程序I/O操作已完成,而完成例程則指定應(yīng)用程序在完成I/O操作后調(diào)用一個事先定義的回調(diào)函數(shù)。5完成端口模型完成端口(Completion port)是一種在Windows服務(wù)平臺上比較成熟和高效的I/O操作方法,它使用線程池處理異步I/O請求。利用完成

7、端口模型,應(yīng)用程序可以管理成百上千個套接字??梢园淹瓿啥丝诳闯上到y(tǒng)維護(hù)的一個隊列,操作系統(tǒng)把重疊I/O操作完成的事件通知放到該隊列中,因此稱其為“完成”端口。當(dāng)套接字被創(chuàng)建后,可以將其與一個完成端口聯(lián)系起來。一個應(yīng)用程序可以創(chuàng)建多個工作線程用于處理完成端口上的通知事件。通常應(yīng)該為每個CPU創(chuàng)建一個線程。8.2 阻塞與非阻塞模式Socket編程8.2.1 設(shè)置非阻塞模式套接字8.2.2 非阻塞模式服務(wù)器應(yīng)用程序編程實(shí)例8.2.3 非阻塞模式客戶端應(yīng)用程序編程實(shí)例8.2.4 基于非阻塞模式的多線程服務(wù)器應(yīng)用程序編程實(shí)例8.2.1 設(shè)置非阻塞模式套接字可以調(diào)用ioctlsocket()函數(shù)將套接字設(shè)

8、置為非阻塞模式,語法如下:int ioctlsocket( SOCKET s, long cmd, u_long* argp );參數(shù)說明如下:s,套接字句柄。cmd,在套接字s上執(zhí)行的命令,它的可選值如表9.1所示。argp,指針變量,指定cmd命令的參數(shù)。ioctlsocket()函數(shù)示例代碼/-/ 初始化WinsockWSADATA wsaData;int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);if (iResult != NO_ERROR) printf(Error at WSAStartup()n);/-/ 創(chuàng)建 SOCKET 對

9、象SOCKET m_socket;m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (m_socket = INVALID_SOCKET) printf(Error at socket(): %ldn, WSAGetLastError(); WSACleanup(); return;/-/ 設(shè)置套接字為非阻塞模式int iMode = 1;ioctlsocket(m_socket, FIONBIO, (u_long FAR*) &iMode);8.2.2 非阻塞模式服務(wù)器應(yīng)用程序編程實(shí)例【例8.1】本實(shí)例中介紹的非阻塞模式服務(wù)器應(yīng)用

10、程序啟動時將在TCP端口9990上進(jìn)行監(jiān)聽。收到客戶端應(yīng)用程序發(fā)送來的數(shù)據(jù)后,服務(wù)器應(yīng)用程序?qū)⑾蚩蛻舳税l(fā)送一個表示收到數(shù)據(jù)的字符串。如果服務(wù)器收到字符串“quit”,則退出應(yīng)用程序。本實(shí)例的功能和運(yùn)行界面與5.3.11小節(jié)中介紹的實(shí)例相同,不同的是本實(shí)例中采用非阻塞模式的套接字進(jìn)行通信。包含的頭文件。引用的庫文件。常量和變量定義。初始化套接字環(huán)境的代碼。創(chuàng)建用于監(jiān)聽的套接字的代碼。設(shè)置服務(wù)器套接字地址的代碼。綁定套接字sServer到本地地址的代碼。在套接字sServer上進(jìn)行監(jiān)聽的代碼。關(guān)閉套接字,釋放資源。8.2.3 非阻塞模式客戶端應(yīng)用程序編程實(shí)例【例8.2】本實(shí)例中介紹的非阻塞模式客戶

11、端應(yīng)用程序啟動時將自動連接到指定服務(wù)器的TCP端口9990,然后提示用戶輸入向服務(wù)器發(fā)送的字符串。實(shí)現(xiàn)服務(wù)器程序的項目為TcpServer,下面介紹服務(wù)器程序的實(shí)現(xiàn)過程。本實(shí)例在下面步驟中的代碼與5.3.12小節(jié)中介紹的實(shí)例相同,請參照理解。包含的頭文件。引用的庫文件。常量和變量定義。初始化套接字環(huán)境的代碼。設(shè)置服務(wù)器套接字地址的代碼。關(guān)閉套接字,釋放資源。8.2.4 基于非阻塞模式的多線程服務(wù)器應(yīng)用程序編程實(shí)例【例8.3】設(shè)計一個基于非阻塞模式的多線程服務(wù)器應(yīng)用程序。主函數(shù)(即主線程)_tmain()負(fù)責(zé)接收來自客戶端的連接請求,然后創(chuàng)建專門與該客戶端進(jìn)行通信的線程。程序在成功調(diào)用accep

12、t()函數(shù)接受來自客戶端的連接請求后,直接調(diào)用CreateThread()創(chuàng)建一個專門的通信線程,線程函數(shù)為AnswerThread,建立連接后得到的套接字sClient作為線程參數(shù)。創(chuàng)建通信線程后,主線程立即返回while循環(huán)的開始部分,繼續(xù)調(diào)用accept()函數(shù)接收其他客戶端的連接請求。這樣就可以同時與多個客戶端進(jìn)行通信了。8.3 基于Select模型的Socket編程8.3.1 select()函數(shù)8.3.2 基于Select模型的服務(wù)器應(yīng)用程序?qū)嵗?.3.1 select()函數(shù)select()函數(shù)可以決定一組套接字的狀態(tài),通常用于操作處于就緒狀態(tài)的套接字。在select()函數(shù)中使

13、用fd_set結(jié)構(gòu)體來管理多個套接字,定義代碼如下:typedef struct fd_set u_int fd_count; SOCKET fd_arrayFD_SETSIZE; fd_set;select()函數(shù)的語法int select( int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout );參數(shù)說明如下:nfds,只為與Berkeley套接字相兼容而保留此參數(shù),在執(zhí)行函數(shù)時會被忽略。readfds,用于檢測可讀性的套接字集合。writefds,用于檢

14、測可寫性的套接字集合。exceptfds,用于檢測存在錯誤的套接字集合。timeout,select()函數(shù)等待的最長時間。如果是阻塞模式的操作,則將此參數(shù)設(shè)置為null。winsock2.h中定義的宏FD_CLR(s, *set):從集合中刪除指定的套接字。FD_ISSET(s, *set):如果參數(shù)s是集合中的成員,則返回非0值,否則返回0。FD_SET(s, *set):向集合中添加套接字。FD_ZERO(s, *set):將集合初始化為空集合。8.3.2 基于Select模型的服務(wù)器應(yīng)用程序?qū)嵗纠?.4】設(shè)計一個基于Select模型的回顯服務(wù)器應(yīng)用程序。所謂回顯,即將它收到的來自客戶

15、端的字符串再發(fā)送回客戶端。假定本實(shí)例中的項目名稱為SelectServer。1結(jié)構(gòu)體SOCKET_INFORMATIONtypedef struct _SOCKET_INFORMATION CHAR BufferDATA_BUFSIZE;/ 發(fā)送和接收數(shù)據(jù)的緩沖區(qū) / 定義發(fā)送和接收數(shù)據(jù)緩沖區(qū)的結(jié)構(gòu)體,包括緩沖區(qū)的長度和內(nèi)容 WSABUF DataBuf; SOCKET Socket;/ 與客戶端進(jìn)行通信的套接字 DWORD BytesSEND;/ 保存套接字發(fā)送的字節(jié)數(shù) DWORD BytesRECV;/ 保存套接字接收的字節(jié)數(shù) SOCKET_INFORMATION, * LPSOCKET_

16、INFORMATION; 結(jié)構(gòu)體SOCKET_INFORMATION中包含用于通信的套接字、發(fā)送和接收數(shù)據(jù)的緩沖區(qū)、發(fā)送和接收的字節(jié)數(shù)等。WSABUF是winsock2.h中定義的結(jié)構(gòu)體,用于保存緩沖區(qū)的地址和長度,代碼如下:typedef struct _WSABUF u_long len; /* the length of the buffer */ char FAR * buf; /* the pointer to the buffer */ WSABUF, FAR * LPWSABUF;2變量定義為了對所有與客戶端進(jìn)行通信的套接字信息進(jìn)行統(tǒng)一管理,需要定義下面兩個變量:/ 記錄正在使用

17、的套接字總數(shù)量DWORD TotalSockets = 0;/ 保存Socket信息對象的數(shù)組,F(xiàn)D_SETSIZE表示SELECT模型中允許的最大套接字?jǐn)?shù)量LPSOCKET_INFORMATION SocketArrayFD_SETSIZE;常量FD_SETSIZE等于64,因為默認(rèn)情況下select()函數(shù)最多可以處理64個套接字。3創(chuàng)建套接字信息BOOL CreateSocketInformation(SOCKET s) LPSOCKET_INFORMATION SI;/ 用于保存套接字的信息 / 為SI分配內(nèi)存空間 if (SI = (LPSOCKET_INFORMATION) Glo

18、balAlloc(GPTR, sizeof(SOCKET_INFORMATION) = NULL) printf(GlobalAlloc() failed with error %dn, GetLastError(); return FALSE; / 初始化SI的值 SI-Socket = s; SI-BytesSEND = 0; SI-BytesRECV = 0; / 在SocketArray數(shù)組中增加一個新元素,用于保存SI對象 SocketArrayTotalSockets = SI; TotalSockets+;/ 增加套接字?jǐn)?shù)量 return(TRUE); 4釋放套接字信息void

19、FreeSocketInformation(DWORD Index) / 獲取指定索引對應(yīng)的LPSOCKET_INFORMATION對象 LPSOCKET_INFORMATION SI = SocketArrayIndex; DWORD i; closesocket(SI-Socket); / 關(guān)閉套接字 /printf(Closing socket number %dn, SI-Socket); / 釋放指定LPSOCKET_INFORMATION對象資源 GlobalFree(SI); / 將數(shù)組中index索引后面的元素前移 for (i = Index; i Socket = s;SI

20、-Next = SocketInfoList;/ Next指定當(dāng)前的SocketInfoListSocketInfoList = SI;/ 把SI放到SocketInfoList的第位4獲取套接字信息LPSOCKET_INFORMATION GetSocketInformation(SOCKET s) SOCKET_INFORMATION *SI = SocketInfoList; while(SI) if (SI-Socket = s) return SI; SI = SI-Next; return NULL;5創(chuàng)建接收消息的窗口HWND MakeWorkerWindow(void)WNDC

21、LASSEX wndclass;/ 定義RegisterClass()函數(shù)注冊的窗口類屬性CHAR *ProviderClass = AsyncSelect;/ 窗口類名HWND Window;/ 新建的窗口句柄/ CS_HREDRAW指定當(dāng)窗口移動或者改變大小時,如果改變了窗口的寬度,則重畫整個窗口/ CS_VREDRAW指定當(dāng)窗口移動或者改變大小時,如果改變了窗口的高度,則重畫整個窗口wndclass.style = CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc = (WNDPROC)WindowProc;/ 指定窗口例程wndclass.cbC

22、lsExtra = 0;/ 指定窗口類結(jié)構(gòu)體后面額外分配的字節(jié)數(shù)wndclass.cbWndExtra = 0;/ 指定窗口實(shí)例后面額外分配的字節(jié)數(shù)wndclass.hInstance = NULL;/ 包含窗口類對應(yīng)的窗口例程的實(shí)例句柄wndclass.hIcon = LoadIcon(NULL, IDI_WARNING);/ 類圖標(biāo)句柄wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);/ 窗口類所使用的光標(biāo)句柄wndclass.hbrBackground =/ 窗口類中用于畫窗口背景的刷子句柄(白色)(HBRUSH) GetStockObject

23、(WHITE_BRUSH);wndclass.lpszMenuName = NULL;/ 窗口類的菜單資源名稱wndclass.lpszClassName = ProviderClass;/ 指定窗口類的名稱為AsyncSelect5創(chuàng)建接收消息的窗口/ 注冊窗口類if (RegisterClassEx(&wndclass) = 0)printf(RegisterClass() failed with error %dn, GetLastError();return NULL;/ 創(chuàng)建接收消息的窗口if (Window = CreateWindow(ProviderClass,WS_OVERL

24、APPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,NULL,NULL) = NULL)printf(CreateWindow() failed with error %dn, GetLastError();return NULL;return Window;6窗口例程如果接收到用戶自定義消息WM_SOCKET,則WindowProc()函數(shù)對該消息進(jìn)行如下處理:(1)如果消息類型是FD_ACCEPT,則調(diào)用accept()函數(shù)接收客戶端的連接請求,并得到與客戶端通信的套接字Accept。(

25、2)如果消息類型是FD_READ,則調(diào)用recv()函數(shù)接收客戶端發(fā)送的信息,并打印接收到的字符串。如果接收到其他消息,則調(diào)用默認(rèn)的窗口例程DefWindowProc()函數(shù)來處理用戶自定義消息。7主函數(shù)程序的執(zhí)行過程如下: (1)調(diào)用MakeWorkerWindow()函數(shù)創(chuàng)建接收消息的窗口。(2)創(chuàng)建用于監(jiān)聽客戶端連接的套接字sockfd。(3)綁定套接字sockfd到本地地址的9990端口。(4)在套接字sockfd上監(jiān)聽。(5)在while循環(huán)中調(diào)用GetMessage()函數(shù)從消息隊列中獲取一個消息。該函數(shù)在winuser.h中聲明。如果獲取消息失敗,則程序返回,否則一直循環(huán)獲取消息

26、。(6)調(diào)用TranslateMessage()函數(shù)將虛擬鍵消息轉(zhuǎn)換為字符串消息。該函數(shù)在winuser.h中聲明。(7)調(diào)用DispatchMessage()函數(shù)將消息轉(zhuǎn)發(fā)到指定的窗口例程進(jìn)行處理。該函數(shù)在winuser.h中聲明。8.5 基于WSAEventSelect模型的Socket編程8.5.1 WSAEventSelect()函數(shù)8.5.2 創(chuàng)建和管理事件對象8.5.3 WSAWaitForMultipleEvents()函數(shù)8.5.4 WSAEnumNetworkEvents()函數(shù)8.5.5 基于WSAEventSelect模型的服務(wù)器編程8.5.1 WSAEventSelec

27、t()函數(shù)WSAEventSelect()函數(shù)的語法如下:int WSAEventSelect( SOCKET s, WSAEVENT hEventObject, long lNetworkEvents);參數(shù)說明如下:s,套接字。hEventObject,與網(wǎng)絡(luò)事件集合相關(guān)聯(lián)的事件對象句柄。lNetworkEvents,感興趣的網(wǎng)絡(luò)事件集合。如果函數(shù)執(zhí)行成功,則返回0,否則返回SOCKET_ERROR。Windows Sockets中定義的網(wǎng)絡(luò)事件網(wǎng) 絡(luò) 事 件說 明事件觸發(fā)時調(diào)用的函數(shù)FD_READ應(yīng)用程序想接收可讀的通知recv、recvfrom、WSARecv或WSARecvFromF

28、D_WRITE應(yīng)用程序想接收可讀寫的通知send、sendto、WSASend或WSASendToFD_OOB應(yīng)用程序想接收帶外數(shù)據(jù)到達(dá)的通知recv、recvfrom、WSARecv或WSARecvFromFD_ACCEPT應(yīng)用程序想接收等待接收連接的通知accept或者WSAAcceptFD_CONNECT應(yīng)用程序想接收連接操作完成的通知無FD_CLOSE應(yīng)用程序想接收套接字關(guān)閉的通知無FD_QOS應(yīng)用程序想接收套接字服務(wù)質(zhì)量發(fā)生變化的通知使用SIO_GET_QOS命令的WSAIoctl()函數(shù)FD_GROUP_QOS應(yīng)用程序想接收套接字組服務(wù)質(zhì)量發(fā)生變化的通知保留FD_ROUTING_I

29、NTERFACE_CHANGE應(yīng)用程序想接收在指定的方向上與路由接口發(fā)生變化的通知使用SIO_ROUTING_INTERFACE_CHANGE.命令的WSAIoctl()函數(shù)FD_ADDRESS_LIST_CHANGE應(yīng)用程序想接收針對套接字的協(xié)議家族、本地地址列表發(fā)生變化的通知使用SIO_ADDRESS_LIST_CHANGE.命令的WSAIoctl()函數(shù)將網(wǎng)絡(luò)事件集合關(guān)聯(lián)到事件對象例如,為套接字s注冊網(wǎng)絡(luò)事件FD_READ和FD_WRITE,并將網(wǎng)絡(luò)事件集合關(guān)聯(lián)到事件對象hEventObject,代碼如下:rc = WSAEventSelect(s, hEventObject, FD_R

30、EAD|FD_WRITE);WSAEventSelect()函數(shù)自動將套接字設(shè)置為非阻塞方式。如果需要將套接字s設(shè)置為阻塞模式,則首先必須清除與套接字s相關(guān)聯(lián)的事件記錄。通過調(diào)用WSAEventSelect()函數(shù),并將lNetworkEvents參數(shù)設(shè)置為0可以實(shí)現(xiàn)此功能,代碼如下:rc = WSAEventSelect(s, hEventObject, 0);然后調(diào)用ioctlsocket()或者WSAIoctl()針套接字設(shè)置為阻塞模式。8.5.2 創(chuàng)建和管理事件對象1創(chuàng)建事件對象2重置事件對象3設(shè)置事件對象為已授信狀態(tài)4關(guān)閉事件對象句柄1創(chuàng)建事件對象調(diào)用WSACreateEvent()

31、函數(shù)可以實(shí)現(xiàn)此功能,語法如下:WSAEVENT WSACreateEvent(void);如果執(zhí)行成功,則函數(shù)返回事件對象的句柄,否則返回WSA_INVALID_EVENT。Windows事件對象分為兩種工作狀態(tài),即已授信(signaled)狀態(tài)和未授信(nonsignaled)狀態(tài);同時Windows事件對象又分為兩種工作模式,即人工重置(manual reset)模式和自動重置(auto reset)模式。調(diào)用WSACreateEvent()函數(shù)創(chuàng)建的事件對象處于人工重置模式和非受信狀態(tài)。2重置事件對象當(dāng)網(wǎng)絡(luò)事件發(fā)生時,與套接字相關(guān)聯(lián)的事件對象被從未授信狀態(tài)轉(zhuǎn)換為已授信狀態(tài)。調(diào)用WSARe

32、setEvent()函數(shù)可以將事件對象從已授信狀態(tài)修改為未授信狀態(tài),語法如下:BOOL WSAResetEvent( WSAEVENT hEvent );參數(shù)hEvent為事件對象句柄。如果函數(shù)執(zhí)行成功,則返回TRUE,否則返回FALSE。 3設(shè)置事件對象為已授信狀態(tài)調(diào)用WSASetEvent()函數(shù)可以將指定事件對象設(shè)置為已授信狀態(tài),語法如下:BOOL WSASetEvent( WSAEVENT hEvent );參數(shù)hEvent為事件對象句柄。如果函數(shù)執(zhí)行成功,則返回TRUE,否則返回FALSE。 4關(guān)閉事件對象句柄在處理完網(wǎng)絡(luò)事件后,需要調(diào)用WSACloseEvent()函數(shù),關(guān)閉事件對

33、象句柄,釋放事件對象占用的資源,語法如下:BOOL WSACloseEvent( WSAEVENT hEvent );參數(shù)hEvent為事件對象句柄。如果函數(shù)執(zhí)行成功,則返回TRUE,否則返回FALSE。8.5.3 WSAWaitForMultipleEvents()函數(shù)WSAWaitForMultipleEvents()函數(shù)的語法如下:DWORD WSAWaitForMultipleEvents( DWORD cEvents, const WSAEVENT* lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable );參數(shù)說明cE

34、vent,指定在參數(shù)lphEvents指向的數(shù)組中包含的事件對象句柄數(shù)量。lphEvents,指向事件對象句柄數(shù)組的指針。fWaitAll,指定等待的類型。如果該參數(shù)等于TRUE,則數(shù)組lphEvents中包含的所有事件對象都變成已授信狀態(tài)時,函數(shù)才返回;如果該參數(shù)等于FALSE,則只要有一個事件對象變成已授信狀態(tài),函數(shù)就返回。在后面的情況中,函數(shù)的返回值就是已授信事件對象的句柄。dwTimeout,指定超時時間。如果該參數(shù)為0,則函數(shù)檢查事件對象的狀態(tài)后立即返回;如果該參數(shù)為WSA_INFINITE,則該函數(shù)會無限期等待,直到滿足參數(shù)fWaitAll指定的條件。如果WSAWaitForMul

35、tipleEvents()函數(shù)調(diào)用超時,則函數(shù)的返回值為WSA_WAIT_TIMEOUT。fAlertable,指定當(dāng)完成例程在系統(tǒng)隊列中排隊等待執(zhí)行時,函數(shù)是否返回。如果該參數(shù)為TRUE,則說明該函數(shù)返回時完成例程已經(jīng)被執(zhí)行;如果該參數(shù)為FALSE,則說明該函數(shù)返回時完成例程還沒有被執(zhí)行。該參數(shù)主要應(yīng)用于重疊I/O模型,具體情況將在8.6小節(jié)中介紹。返回值如果函數(shù)執(zhí)行成功,返回返回值表示已授信事件對象的句柄;如果函數(shù)執(zhí)行失敗,則返回WSA_WAIT_FAILED。調(diào)用后調(diào)用后,WSAWaitForMultipleEvents()函數(shù)處于阻塞狀態(tài),直到下面兩種情況之一才會返回:(1)指定的一個

36、或所有事件對象處于已授信狀態(tài),即發(fā)生了指定的網(wǎng)絡(luò)事件。(2)阻塞時間超過指定的超時時間。8.5.4 WSAEnumNetworkEvents()函數(shù)WSAEnumNetworkEvents()函數(shù)可以返回發(fā)生網(wǎng)絡(luò)事件的套接字,語法如下:int WSAEnumNetworkEvents( SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );參數(shù)說明如下:s,發(fā)生網(wǎng)絡(luò)事件的套接字句柄。hEventObject,可選參數(shù),指定被重置的事件對象句柄。lpNetworkEvents,指向LPWSANETWORKEVE

37、NTS結(jié)構(gòu)體的指針,其中保存發(fā)生的網(wǎng)絡(luò)事件記錄和相關(guān)聯(lián)的錯誤編碼。LPWSANETWORKEVENTS結(jié)構(gòu)體typedef struct _WSANETWORKEVENTS long lNetworkEvents; int iErrorCodeFD_MAX_EVENTS; WSANETWORKEVENTS, *LPWSANETWORKEVENTS;參數(shù)說明如下:lNetworkEvents,指定發(fā)生的網(wǎng)絡(luò)事件。iErrorCoe,包含相關(guān)錯誤碼的數(shù)組,數(shù)組索引與lNetworkEvents字段中的網(wǎng)絡(luò)事件位相對應(yīng)。8.5.5 基于WSAEventSelect模型的服務(wù)器編程開發(fā)基于WSAEve

38、ntSelect模型的Socket通信服務(wù)器的基本流程如下:(1)初始化Windows Sockets環(huán)境,并創(chuàng)建用于監(jiān)聽的套接字。(2)創(chuàng)建事件對象。(3)將新建的事件對象與監(jiān)聽套接字相關(guān)聯(lián),并注冊該套接字關(guān)注的網(wǎng)絡(luò)事件集合,通常為FD_ACCEPT和FD_CLOSE。(4)等待所有事件對象上發(fā)生注冊的網(wǎng)絡(luò)事件,并對網(wǎng)絡(luò)事件進(jìn)行處理。(5)如果觸發(fā)了FD_ACCEPT事件,則程序接收來自客戶端的請求,得到與客戶端通信的套接字,并為該套接字創(chuàng)建相關(guān)聯(lián)的事件對象,注冊該套接字關(guān)注的網(wǎng)絡(luò)事件集合,通常為FD_READ、FD_CLOSE和FD_WRITE。(6)如果觸發(fā)了FD_CLOSE事件,則關(guān)閉

39、套接字,釋放其占用的資源。(7)如果觸發(fā)了FD_READ事件,則調(diào)用recv()函數(shù)接收來自客戶端的請求。(8)如果觸發(fā)了FD_WRITE事件,則調(diào)用send()函數(shù)向客戶端發(fā)送數(shù)據(jù)?!纠?.6】1變量定義2創(chuàng)建和綁定套接字,并將其設(shè)置為監(jiān)聽狀態(tài)3創(chuàng)建事件對象,并注冊網(wǎng)絡(luò)事件4處理網(wǎng)絡(luò)事件程序的執(zhí)行過程如下:(1)調(diào)用WSAWaitForMultipleEvents()函數(shù)在所有事件對象上等待,只要有一個事件對象變?yōu)橐咽谛艩顟B(tài),則函數(shù)返回。(2)依次對所有的事件對象調(diào)用WSAWaitForMultipleEvents()函數(shù),以判斷它們是否觸發(fā)了注冊的網(wǎng)絡(luò)事件。(3)對于由每個觸發(fā)了網(wǎng)絡(luò)事件的

40、事件對象,調(diào)用WSAEnumNetworkEvents()函數(shù)重置事件對象的狀態(tài),表示該網(wǎng)絡(luò)事件已經(jīng)被處理。(4)如果觸發(fā)的網(wǎng)絡(luò)事件是FD_ACCEPT,則調(diào)用accept()函數(shù)接收來自客戶端的連接請求,并將得到的與客戶端進(jìn)行通信的套接字sNew添加到sockArray數(shù)組中。然后為套接字sNew創(chuàng)建相關(guān)聯(lián)的事件對象newEvent,再將newEvent添加到eventArray數(shù)組中。(5)如果觸發(fā)的網(wǎng)絡(luò)事件是FD_READ,則調(diào)用recv()函數(shù)接收客戶端發(fā)送的消息,然后調(diào)用send()函數(shù)發(fā)送回應(yīng)消息。(6)如果觸發(fā)的網(wǎng)絡(luò)事件是FD_WRITE,則說明前面發(fā)送消息沒有成功,此時需要重新

41、調(diào)用send()函數(shù)發(fā)送回應(yīng)消息。(7)如果觸發(fā)的網(wǎng)絡(luò)事件是FD_CLOSE,則關(guān)閉套接字,釋放資源,并從數(shù)組sockArray中刪除該套接字。8.6 基于重疊I/O模型的Socket編程8.6.1 WSASocket()函數(shù)8.6.2 調(diào)用WSASend()函數(shù)發(fā)送數(shù)據(jù)8.6.3 調(diào)用WSARecv()函數(shù)接收數(shù)據(jù)8.6.4 GetOverlappedResult()函數(shù)8.6.5 使用事件通知來管理重疊I/O操作8.6.6 使用完成例程來管理重疊I/O操作8.6.1 WSASocket()函數(shù)WSASocket()函數(shù)用于創(chuàng)建綁定到指定的傳輸服務(wù)提供程序的套接字,語法如下:SOCKET W

42、SASocket( int af, int type, int protocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags );參數(shù)說明af,指定地址家族,通常使用AF_INET參數(shù)。type,指定新建套接字的類型。SOCK_STREAM指定新建基于流的套接字,SOCK_DGRAM指定新建基于數(shù)據(jù)報的套接字。protocol,指定套接字使用的協(xié)議,例如IPPROTO_TCP表示套接字協(xié)議為TCP,IPPROTO_UDP表示套接字協(xié)議為UDP。lpProtocolInfo,指向WSAPROTOCOL_INFO結(jié)構(gòu)體,指定

43、新建套接字的特性。g,預(yù)留字段。dwFlags,指定套接字屬性的標(biāo)識。在重疊I/O模型中,dwFlags參數(shù)需要被設(shè)置為WSA_FLAG_OVERLAPPED,這樣就可以創(chuàng)建一個重疊套接字。重疊套接字可以使用WSASend()、WSASendTo()、WSARecv()、WSARecvFrom()和WSAIoctl()等函數(shù)執(zhí)行重疊I/O操作,即同時初始化和處理多個操作。WSASocket()函數(shù)的應(yīng)用實(shí)例#include winsock2.hvoid main() WSADATA wsaData; SOCKET RecvSocket; /- / Initialize Winsock WSAS

44、tartup(MAKEWORD(2,2), &wsaData); /- / Create a socket RecvSocket = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, WSA_FLAG_OVERLAPPED); 8.6.2 調(diào)用WSASend()函數(shù)發(fā)送數(shù)據(jù)WSASend()函數(shù)可以在連接的套接字上發(fā)送數(shù)據(jù),語法如下:int WSASend( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFla

45、gs, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );參數(shù)說明s,用于通信的套接字。lpBuffers,指向WSABUF結(jié)構(gòu)體的指針。WSABUF結(jié)構(gòu)體中包含指向緩沖區(qū)的指針和緩沖區(qū)的長度。dwBufferCount,lpBuffers數(shù)組中WSABUF結(jié)構(gòu)體的數(shù)量。lpNumberOfBytesSent,如果I/O操作立即完成,則該參數(shù)指定發(fā)送數(shù)據(jù)的字節(jié)數(shù)。dwFlags,用于修改WSASend()函數(shù)行為的標(biāo)識位。lpOverlapped,指向WSAOVERLAP

46、PED結(jié)構(gòu)體的指針。該參數(shù)對于非重疊套接字無效。lpCompletionRoutine,指向完成例程。完成例程是在發(fā)送操作完成后調(diào)用的函數(shù)。該參數(shù)對于非重疊套接字無效。WSASend()函數(shù)返回結(jié)果如果重疊操作立即完成,則WSASend()函數(shù)返回0,并且參數(shù)lpNumberOfBytesSent被更新為發(fā)送數(shù)據(jù)的字節(jié)數(shù);如果重疊操作被成功初始化,并且將在稍后完成,則WSASend()函數(shù)返回SOCKET_ERROR,錯誤代碼為WSA_IO_PENDING。在后面的情況下,參數(shù)lpNumberOfBytesSend的值并不被更新。當(dāng)重疊操作完成后,可以通過下面兩種方式獲取傳輸數(shù)據(jù)的數(shù)量:如果指

47、定了完成例程,則通過完成例程的cbTransferred參數(shù)獲取。關(guān)于完成例程的情況將在稍后介紹。通過WSAGetOverlappedResult()函數(shù)的lpcbTransfer參數(shù)獲取。WSAOVERLAPPED結(jié)構(gòu)體WSAOVERLAPPED結(jié)構(gòu)體是重疊I/O模型中的核心,在WSASend()和WSARecv()函數(shù)中都需要指定需要綁定的WSAOVERLAPPED結(jié)構(gòu)體對象。WSAOVERLAPPED結(jié)構(gòu)體的定義代碼如下:typedef struct _WSAOVERLAPPED DWORD Internal; DWORD InternalHigh; DWORD Offset; DWOR

48、D OffsetHigh; WSAEVENT hEvent; WSAOVERLAPPED, *LPWSAOVERLAPPED;參數(shù)說明Internal,由重疊I/O實(shí)現(xiàn)的實(shí)體內(nèi)部使用的字段。在使用套接字的情況下,該字段被底層操作系統(tǒng)使用。InternalHigh,由重疊I/O實(shí)現(xiàn)的實(shí)體內(nèi)部使用的字段。在使用套接字的情況下,該字段被底層操作系統(tǒng)使用。Offset,在使用套接字的情況下該參數(shù)會被忽略。OffsetHigh,在使用套接字的情況下該參數(shù)會被忽略。hEvent,如果重疊I/O操作在被調(diào)用時沒有使用I/O操作完成例程(即lpCompletionRoutine為空指針),那么該字段必須包含一

49、個有效的WSAEVENT對象的句柄,否則(lpCompletionRoutine不為空指針),應(yīng)用程序可以視需要使用該字段。8.6.3 調(diào)用WSARecv()函數(shù)接收數(shù)據(jù)WSARecv()函數(shù)可以在已連接的套接字上接收數(shù)據(jù),語法如下int WSARecv( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpComple

50、tionRoutine );參數(shù)說明s,用于通信的套接字。lpBuffers,指向WSABUF結(jié)構(gòu)體的指針。WSABUF結(jié)構(gòu)體中包含指向緩沖區(qū)的指針和緩沖區(qū)的長度。dwBufferCount,lpBuffers數(shù)組中WSABUF結(jié)構(gòu)體的數(shù)量。lpNumberOfBytesRecvd,如果I/O操作立即完成,則該參數(shù)指定接收數(shù)據(jù)的字節(jié)數(shù)。dwFlags,標(biāo)識字段。lpOverlapped,指向WSAOVERLAPPED結(jié)構(gòu)體的指針。該參數(shù)對于非重疊套接字無效。lpCompletionRoutine,指向完成例程。完成例程是在接收操作完成后調(diào)用的函數(shù)。該參數(shù)對于非重疊套接字無效。返回值如果函數(shù)執(zhí)行

51、沒有錯誤,并且重疊操作立即完成,則函數(shù)返回0,并且參數(shù)lpNumberOfBytesRecvd被更新為接收數(shù)據(jù)的字節(jié)數(shù);如果重疊操作被成功初始化,并且將在稍后完成,則函數(shù)返回SOCKET_ERROR,錯誤代碼為WSA_IO_PENDING。在后面的情況下,參數(shù)lpNumberOfBytesRecvd的值并不被更新。8.6.4 GetOverlappedResult()函數(shù)使用GetOverlappedResult()函數(shù)可以獲取指定文件、命名管道和通信設(shè)備上重疊操作的結(jié)果,語法如下:BOOL GetOverlappedResult( HANDLE hFile, LPOVERLAPPED lpO

52、verlapped, LPDWORD lpNumberOfBytesTransferred, BOOL bWait );參數(shù)說明hFile,指定文件、命名管道和通信設(shè)備的句柄。該句柄是在調(diào)用ReadFile()、WriteFile()等函數(shù)開始重疊操作時指定的句柄。lpOverlapped,重疊操作開始時指定的OVERLAPPED結(jié)構(gòu)體。lpNumberOfBytesTransferred,指向在讀、寫操作中實(shí)際傳輸?shù)淖止?jié)數(shù)的變量。bWait,如果該參數(shù)為TRUE,則函數(shù)會一直等待到操作完成后返回;否則函數(shù)會直接返回。如果函數(shù)執(zhí)行成功,則返回非0值,否則函數(shù)當(dāng)返回0。8.6.5 使用事件通知來

53、管理重疊I/O操作在WSASend()或WSARecv()函數(shù)中,當(dāng)重疊操作完成后,如果lpCompletionRoutine參數(shù)為NULL,則lpOverlapped中的hEvent參數(shù)將被設(shè)置為已授信狀態(tài)。應(yīng)用程序可以調(diào)用WSAWaitForMultipleEvents()函數(shù)或者WSAGetOverlappedResult()函數(shù)等待或輪循事件對象變成未授信狀態(tài)。【例8.7】演示使用事件通知方法來管理重疊I/O操作。1變量定義2創(chuàng)建和綁定套接字,并將其設(shè)置為監(jiān)聽狀態(tài),接受客戶端的連接請求3創(chuàng)建事件對象,并初始化重疊結(jié)構(gòu)4接收數(shù)據(jù),并將數(shù)據(jù)回發(fā)到客戶端1變量定義int _tmain(int

54、 argc, _TCHAR* argv)/-/ 聲明和初始化變量WSABUF DataBuf;/ 發(fā)送和接收數(shù)據(jù)的緩沖區(qū)結(jié)構(gòu)體char bufferDATA_BUFSIZE;/ 緩沖區(qū)結(jié)構(gòu)體DataBuf中DWORD EventTotal = 0,/ 記錄事件對象數(shù)組中的數(shù)據(jù)RecvBytes = 0,/ 接收的字節(jié)數(shù)Flags = 0,/ 標(biāo)識位BytesTransferred = 0;/ 在讀、寫操作中實(shí)際傳輸?shù)淖止?jié)數(shù)/ 數(shù)組對象數(shù)組WSAEVENT EventArrayWSA_MAXIMUM_WAIT_EVENTS;WSAOVERLAPPED AcceptOverlapped;/ 重疊結(jié)

55、構(gòu)體SOCKET ListenSocket, AcceptSocket;/ 監(jiān)聽套接字和與客戶端進(jìn)行通信的套接字 2創(chuàng)建和綁定套接字,并將其設(shè)置為監(jiān)聽狀態(tài),接受客戶端的連接請求int _tmain(int argc, _TCHAR* argv) /-/ 初始化Windows SocketsWSADATA wsaData;WSAStartup(MAKEWORD(2,2), &wsaData);/-/ 創(chuàng)建監(jiān)聽套接字,并將其綁定到本地IP地址和端口ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);u_short port = 9990

56、;char* ip;sockaddr_in service;2創(chuàng)建和綁定套接字,并將其設(shè)置為監(jiān)聽狀態(tài),接受客戶端的連接請求service.sin_family = AF_INET;service.sin_port = htons(port);hostent* thisHost;thisHost = gethostbyname();ip = inet_ntoa (*(struct in_addr *)*thisHost-h_addr_list);service.sin_addr.s_addr = inet_addr(ip);bind(ListenSocket, (SOCKADDR *) &ser

57、vice, sizeof(SOCKADDR);/-/ 開始監(jiān)聽listen(ListenSocket, 1);printf(Listening.n);/-/ 接收連接請求AcceptSocket = accept(ListenSocket, NULL, NULL);printf(Client Accepted.n); 3創(chuàng)建事件對象,并初始化重疊結(jié)構(gòu)int _tmain(int argc, _TCHAR* argv) / 創(chuàng)建事件對象,建立重疊結(jié)構(gòu)EventArrayEventTotal = WSACreateEvent();ZeroMemory(buffer, DATA_BUFSIZE);Z

58、eroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED);/ 初始化重疊結(jié)構(gòu)AcceptOverlapped.hEvent = EventArrayEventTotal;/ 設(shè)置重疊結(jié)構(gòu)中的hEvent字段DataBuf.len = DATA_BUFSIZE;/ 設(shè)置緩沖區(qū)DataBuf.buf = buffer;EventTotal+;/ 事件對象總數(shù)加 4接收數(shù)據(jù),并將數(shù)據(jù)回發(fā)到客戶端程序的執(zhí)行過程如下: (1)調(diào)用WSARecv()函數(shù)異步接收來自客戶端的數(shù)據(jù),即調(diào)用該函數(shù)后,無論是否能夠接收到數(shù)據(jù)都會返回。(2)調(diào)用WSAWaitForMul

59、tipleEvents()函數(shù)在所有事件對象上等待,只要有一個事件對象變?yōu)橐咽谛艩顟B(tài),則函數(shù)返回。(3)調(diào)用WSAGetOverlappedResult()函數(shù)獲取套接字AcceptSocket上重疊操作的狀態(tài)到AcceptOverlapped結(jié)構(gòu)體中。(4)以前面獲取到的結(jié)構(gòu)體AcceptOverlapped為參數(shù)調(diào)用WSASend()函數(shù),向客戶端發(fā)送接收到的數(shù)據(jù)。(5)初始化事件對象、緩沖區(qū)、重疊結(jié)構(gòu)和標(biāo)識位等,以便處理下次接收和發(fā)送數(shù)據(jù)的操作。8.6.6 使用完成例程來管理重疊I/O操作完成例程的語法如下:void CALLBACK CompletionROUTINE( IN DWOR

60、D dwError, IN DWORD cbTransferred, IN LPWSAOVERLAPPED lpOverlapped, IN DWORD dwFlags);參數(shù)說明如下:dwError,指定lpOverlapped參數(shù)中表示的重疊操作的完成狀態(tài)。cbTransferred,指定發(fā)送的字節(jié)數(shù)。lpOverlapped,指定重疊操作的結(jié)構(gòu)體。dwFlags,返回操作結(jié)束時可能用的標(biāo)志,通??梢栽O(shè)置為0。完成例程沒有返回值。【例8.8】演示使用完成例程方法來管理重疊I/O操作。1常量和變量定義2PER_IO_OPERATION_DATA結(jié)構(gòu)體3工作線程函數(shù)WorkerThread()

溫馨提示

  • 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

提交評論