上1文中我們介紹了使用基于事件進行IO堆疊的方法,本文主要介紹另外1種,基于回調函數void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags)進行IO堆疊。首先,我們先介紹1種線程的狀態——alertable wait狀態。
甚么是alertable wait狀態?
alertable wait狀態是線程等待接收操作系統消息的狀態,我們之前接觸過的WSAWaitForMultipleEvents函數就能夠觸發線程這類狀態。我們先來回顧下WSAWaitForMultipleEvents的函數原型:
DWORDWSAWaitForMultipleEvents( __in DWORD cEvents, __in const WSAEVENT *lphEvents, __in BOOL fWaitAll, __in DWORD dwTimeout, __in BOOL fAlertable );
它的第5個參數fAlertable就是設置alertablewait狀態的開關,當它設置為True時就會激活線程的alertable wait狀態。我們定義的回調函數CompletionRoutine,只有在線程進入alertablewait狀態后才會被操作系統調用。
我們先來回顧下WSARecv
的原型(WSASend類似):
int WSARecv( __in SOCKET s, __inout LPWSABUF lpBuffers, __in DWORD dwBufferCount, __out LPDWORD lpNumberOfBytesRecvd, __inout LPDWORD lpFlags, __in LPWSAOVERLAPPED lpOverlapped, __in LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );
在上1文中,采取基于事件的IO堆疊時,將WSARecv
最后1個參數設置成了NULL。而實際上他就是我們定義的回調函數
CompletionRoutine的函數指針。而CompletionRoutine函數的后3個參數分別來自WSARecv
的第4~6個參數。當線程進入
alertable wait狀態后,操作系統就會履行CompletionRoutine函數。
上述是使用CompletionRoutine的理論部份,下面我們通過實例對CompletionRoutine進行學習。使用CompletionRoutine進行數據接收的例子:
// WSASocketCompletionRoutineServ.cpp : 定?§義°?控?制?臺??§應®|用®?程¨?序¨°的ì?入¨?口¨2點ì?。?ê // #include "stdafx.h" #include "stdio.h" #include "stdlib.h" #include <WinSock2.h> #include <string.h> #pragma comment(lib,"ws2_32.lib") #define BUF_SIZE 30 void ErrorHandler(const char* message); void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags); char buf[BUF_SIZE]; DWORD recvBytes = 0; WSABUF wsaBuf; int _tmain(int argc, _TCHAR* argv[]) { SOCKETservSock,clntSock; SOCKADDR_INservAddr,clntAddr; int clntAddrSz; WSAOVERLAPPEDoverLapped; HANDLEhEvent; DWORDret,flags=0; WSADATAwsaData; WSAStartup(MAKEWORD(2,2),&wsaData); //創???建?§支?ì持?IO復??用®?的ì?套??á接¨®字á? servSock=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED); if(servSock==INVALID_SOCKET) ErrorHandler("WSASocket Error"); memset(&servAddr,0,sizeof(servAddr)); servAddr.sin_family=AF_INET; servAddr.sin_addr.s_addr=htonl(INADDR_ANY); servAddr.sin_port=htons(atoi("8888")); if(bind(servSock,(SOCKADDR*)&servAddr,sizeof(servAddr))==SOCKET_ERROR) ErrorHandler("bind error"); if(listen(servSock,5)==SOCKET_ERROR) ErrorHandler("listen error"); clntAddrSz=sizeof(clntAddr); clntSock=accept(servSock,(SOCKADDR*)&clntAddr,&clntAddrSz); memset(&overLapped,0,sizeof(overLapped)); wsaBuf.buf=buf; wsaBuf.len=BUF_SIZE; hEvent=WSACreateEvent(); overLapped.hEvent=hEvent; if(WSARecv(clntSock,&wsaBuf,1,&recvBytes,&flags,&overLapped,CompletionRoutine)==SOCKET_ERROR) { if(WSAGetLastError()==WSA_IO_PENDING) puts("background recieve data"); } ret=WSAWaitForMultipleEvents(1,&hEvent,false,WSA_INFINITE,true); if(ret==WAIT_IO_COMPLETION) puts("Overlapped I/O Compeleted"); else ErrorHandler("WSARecv Error"); WSACloseEvent(hEvent); closesocket(servSock); closesocket(clntSock); WSACleanup(); return 0; } void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags) { if(dwError!=0) { ErrorHandler("CompletionRoutine Error"); } else { recvBytes=szRecvBytes; printf("Recieve Message:%s\n",buf); } } void ErrorHandler(const char* message) { fputs(message,stderr); fputc('/n',stderr); exit(1); }
第79~90行,是對CompletionRoutine函數的定義,在函數中操作系統將實際接收的字節szRecvBytes等信息傳遞給我們,在函數里對收到的buf進行打印
第17~19行,分別聲明了用于接收數據的buf,實際接收的字節數recvBytes,和用于WSARecv調用的wsaBuf,由于在回調函數CompletionRoutine中也會用到這些變量,所以它們被聲明成了全局變量。
第55~59行,初始化進行IO堆疊的相干變量,包括wsaBuf、hEvent和overLapped。
第61行,調用WSARecv進行數據的接收,并將定義好的CompletionRoutine函數傳給其最后1個參數。
下面是使用WSASend函數的1個例子,由于上上面的例子原理上相同,這里就不在贅述。
// WSASocketCompletionRoutineClnt.cpp : 定?§義°?控?制?臺??§應®|用®?程¨?序¨°的ì?入¨?口¨2點ì?。?ê // #include "stdafx.h" #include "stdio.h" #include "stdlib.h" #include <WinSock2.h> #include <string.h> #pragma comment(lib,"ws2_32.lib") #define BUF_SIZE 30 void ErrorHandler(const char* message); void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags); char buf[BUF_SIZE]="Hello world"; DWORD recvBytes = 0; int _tmain(int argc, _TCHAR* argv[]) { SOCKETservSock; SOCKADDR_INservAddr; WSAOVERLAPPEDoverLapped; HANDLEhEvent; WSABUFwsaBuf; DWORDret,flags=0; WSADATAwsaData; WSAStartup(MAKEWORD(2,2),&wsaData); //創???建?§支?ì持?IO復??用®?的ì?套??á接¨®字á? servSock=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED); if(servSock==INVALID_SOCKET) ErrorHandler("WSASocket Error"); memset(&servAddr,0,sizeof(servAddr)); servAddr.sin_family=AF_INET; servAddr.sin_addr.s_addr=inet_addr("127.0.0.1"); servAddr.sin_port=htons(atoi("8888")); connect(servSock,(SOCKADDR*)&servAddr,sizeof(servAddr)); memset(&overLapped,0,sizeof(overLapped)); wsaBuf.buf=buf; wsaBuf.len=BUF_SIZE; hEvent=WSACreateEvent(); overLapped.hEvent=hEvent; if(WSASend(servSock,&wsaBuf,1,&recvBytes,flags,&overLapped,CompletionRoutine)==SOCKET_ERROR) { if(GetLastError()==WSA_IO_PENDING) puts("background data send"); } ret=WSAWaitForMultipleEvents(1,&hEvent,false,WSA_INFINITE,true); if(ret==WAIT_IO_COMPLETION) puts("Overlapped I/O Compeleted"); else ErrorHandler("WSASend Error"); WSACloseEvent(hEvent); closesocket(servSock); WSACleanup(); return 0; } void CALLBACK CompletionRoutine(DWORD dwError,DWORDszRecvBytes,LPWSAOVERLAPPED lpOverlapped,DWORD flags) { if(dwError!=0) { ErrorHandler("CompletionRoutine Error"); } else { recvBytes=szRecvBytes; printf("Send Message:%s\n",buf); } } void ErrorHandler(const char* message) { fputs(message,stderr); fputc('/n',stderr); exit(1); }
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本項目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
獲得本文源代碼:
git checkout NL55