WSAEventSelect 是 WinSock 提供的一種異步事件通知I/O模型,與 WSAAsyncSelect模型有些類似。該模型同樣是接收 FD_XXX 之類的網絡事件,但是是通過事件對象句柄通知,而非像 WSAAsyncSelect一樣依靠Windows的消息驅動機制。
與WSAAsyncSelect模型相同,WSAEventSelect將所有的SOCKET事件分為如下類型:(共十種)
FD_READ , FD_WRITE , FD_OOB , FD_ACCEPT, FD_CONNECT , FD_CLOSE,
FD_QOS , FD_GROUP_QOS , FD_ROUTING_INTERFACE_CHANGE , FD_ADDRESS_LIST_CHANGE
還有一個 FD_ALL_EVENTS 代表所有的事件
其中 FD_READ 的定義如下:
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT) // = 1
其他的定義也都類似,比如: FD_ACCEPT_BIT = 3
但是並不是每一種SOCKET都能發生所有的事件,比如監聽SOCKET只能發生 FD_ACCEPT 和 FD_CLOSE 事件。
在WSAEventSelect模型中,基本流程如下:
1. 創建一個事件對象數組,用於存放所有的事件對象;
2. 創建一個事件對象(WSACreateEvent);
3. 將一組你感興趣的SOCKET事件與事件對象關聯(WSAEventSelect),然後加入事件對象數組;
4. 等待事件對象數組上發生一個你感興趣的網絡事件(WSAWaitForMultipleEvents);
5. 對發生事件的事件對象查詢具體發生的事件類型(WSAEnumNetworkEvents);
6. 針對不同的事件類型進行不同的處理;
7. 循環進行 .4
對於TCP服務端程序而言,在創建一個監聽SOCKET,綁定至某個端口然後監聽後,可以創建一個事件對象然後與 FD_ACCEPT 和 FD_CLOSE 事件
關聯。在第6步時對於 FD_ACCEPT 事件可以將accept得到的SOCKET關聯 FD_WRITE,FD_READ,FD_CLOSE事件後加入事件對象數組。
WSAEVENT WSACreateEvent(void);
創建一個 事件對象,實際上 WSAEVENT就是一個 HANDLE
int WSAEventSelect(
_In_ SOCKET s, // 需要關聯的SOCKET
_In_ WSAEVENT hEventObject, // 需要關聯的事件對象
_In_ long lNetworkEvents // 感興趣的網絡事件,不同的事件可以用 | 合並, FD_ALL_EVENTS 代表所有的事件
);
函數執行成功將返回 0 ,否則返回 SOCKET_ERROR, 可調用WSAGetLastError() 查看具體的錯誤代碼
DWORD WSAWaitForMultipleEvents(
_In_ DWORD cEvents, // 事件對象數組的數量
_In_ const WSAEVENT *lphEvents, // 事件對象數組
_In_ BOOL fWaitAll, // 是否等待所有的事件對象受信,顯然一般情況下是false
_In_ DWORD dwTimeout, // 超時時限,單位是毫秒,WSA_INFINITE 為無窮大
_In_ BOOL fAlertable // 該模型下忽略,應該設置為false
);
如果執行失敗返回 WSA_WAIT_IO_COMPLETION ; 如果是超時,則返回 WSA_WAIT_TIMEOUT
如果 函數執行成功將會返回一個值,分布在 區間 [ WSA_WAIT_EVENT_0 ,(WSA_WAIT_EVENT_0+cEvents-1) ] 內
也就是說返回值 nRet-WSA_WAIT_EVENT_0 將是發生事件的對象在事件對象數組中的下標。
int WSAEnumNetworkEvents(
_In_ SOCKET s, // 發生事件的SOCKET
_In_ WSAEVENT hEventObject, // 發生事件的事件對象
_Out_ LPWSANETWORKEVENTS lpNetworkEvents // 發生的網絡事件
);
如果該函數執行成功將會返回0,然後可以通過查詢網絡事件判斷到底發生了什麼事件。
WSANETWORKEVENTS的定義如下:
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents; // 發生的網絡事件類型
int iErrorCode[FD_MAX_EVENTS]; // 網絡事件對應的錯誤代碼
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
比如當發生 FD_READ 事件時, 那麼 networkEvent.lNetworkEvents&FD_READ 將為真,同時 networkEvent.iErrorCode[FD_READ_BIT]
標明了此時的錯誤代碼。
代碼示例:(你還需要一個 client程序,自己寫或者找吧)
#include <Windows.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
using std::cout;
using std::cin;
using std::endl;
using std::ends;
void WSAEventServerSocket()
{
SOCKET server = ::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(server == INVALID_SOCKET){
cout<<"創建SOCKET失敗!,錯誤代碼:"<<WSAGetLastError()<<endl;
return ;
}
int error = 0;
sockaddr_in addr_in;
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(15000);
addr_in.sin_addr.s_addr = INADDR_ANY;
error= ::bind(server,(sockaddr*)&addr_in,sizeof(sockaddr_in));
if(error == SOCKET_ERROR){
cout<<"綁定端口失敗!,錯誤代碼:"<<WSAGetLastError()<<endl;
return ;
}
listen(server,5);
if(error == SOCKET_ERROR){
cout<<"監聽失敗!,錯誤代碼:"<<WSAGetLastError()<<endl;
return ;
}
cout<<"成功監聽端口 :"<<ntohs(addr_in.sin_port)<<endl;
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS]; // 事件對象數組
SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS]; // 事件對象數組對應的SOCKET句柄
int nEvent = 0; // 事件對象數組的數量
WSAEVENT event0 = ::WSACreateEvent();
::WSAEventSelect(server,event0,FD_ACCEPT|FD_CLOSE);
eventArray[nEvent]=event0;
sockArray[nEvent]=server;
nEvent++;
while(true){
int nIndex = ::WSAWaitForMultipleEvents(nEvent,eventArray,false,WSA_INFINITE,false);
if( nIndex == WSA_WAIT_IO_COMPLETION || nIndex == WSA_WAIT_TIMEOUT ){
cout<<"等待時發生錯誤!錯誤代碼:"<<WSAGetLastError()<<endl;
break;
}
nIndex = nIndex - WSA_WAIT_EVENT_0;
WSANETWORKEVENTS event;
SOCKET sock = sockArray[nIndex];
::WSAEnumNetworkEvents(sock,eventArray[nIndex],&event);
if(event.lNetworkEvents & FD_ACCEPT){
if(event.iErrorCode[FD_ACCEPT_BIT]==0){
if(nEvent >= WSA_MAXIMUM_WAIT_EVENTS){
cout<<"事件對象太多,拒絕連接"<<endl;
continue;
}
sockaddr_in addr;
int len = sizeof(sockaddr_in);
SOCKET client = ::accept(sock,(sockaddr*)&addr,&len);
if(client!= INVALID_SOCKET){
cout<<"接受了一個客戶端連接 "<<inet_ntoa(addr.sin_addr)<<":"<<ntohs(addr.sin_port)<<endl;
WSAEVENT eventNew = ::WSACreateEvent();
::WSAEventSelect(client,eventNew,FD_READ|FD_CLOSE|FD_WRITE);
eventArray[nEvent]=eventNew;
sockArray[nEvent]=client;
nEvent++;
}
}
}else if(event.lNetworkEvents & FD_READ){
if(event.iErrorCode[FD_READ_BIT]==0){
char buf[2500];
ZeroMemory(buf,2500);
int nRecv = ::recv( sock,buf,2500,0);
if(nRecv>0){
cout<<"收到一個消息 :"<<buf<<endl;
char strSend[] = "I recvived your message.";
::send(sock,strSend,strlen(strSend),0);
}
}
}else if(event.lNetworkEvents & FD_CLOSE){
::WSACloseEvent(eventArray[nIndex]);
::closesocket(sockArray[nIndex]);
cout<<"一個客戶端連接已經斷開了連接"<<endl;
for(int j=nIndex;j<nEvent-1;j++){
eventArray[j]=eventArray[j+1];
sockArray[j]=sockArray[j+1];
}
nEvent--;
} else if(event.lNetworkEvents & FD_WRITE ){
cout<<"一個客戶端連接允許寫入數據"<<endl;
}
} // end while
::closesocket(server);
}
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsaData;
int error;
WORD wVersionRequested;
wVersionRequested = WINSOCK_VERSION;
error = WSAStartup( wVersionRequested , &wsaData );
if ( error != 0 ) {
WSACleanup();
return 0;
}
WSAEventServerSocket();
WSACleanup();
return 0;
}
// 解釋一下,為什麼我在 socket函數前面加上 ::
因為我前面寫的時候本來用了thread庫准備開一個線程運行Server,另一個運行Client。
結果 用了 using namespace std; 後,正好引入了bind函數(std的那個模板)把 socket的bind給覆蓋了,
然後就一直是 錯誤了,查下錯誤代碼是 10022(無效參數),檢查時才發現的。