WSAEventSelect 是 WinSock 提供的一种异步事件通知I/O模型,与 WSAAsyncSelect模型有些相似。
该模型一样是接收 FD_XXX 之类的网络事件,可是是经过事件对象句柄通知,而非像 WSAAsyncSelect同样依靠Windows的消息驱动机制。ios
与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就是一个 HANDLEsocket
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 将是发生事件的对象在事件对象数组中的下标。spa
int WSAEnumNetworkEvents(
_In_ SOCKET s,
//
发生事件的SOCKET
_In_ WSAEVENT hEventObject,
//
发生事件的事件对象
_Out_ LPWSANETWORKEVENTS lpNetworkEvents
//
发生的网络事件
);
若是该函数执行成功将会返回0,而后能够经过查询网络事件判断到底发生了什么事件。线程
WSANETWORKEVENTS的定义以下:code
typedef
struct _WSANETWORKEVENTS {
long lNetworkEvents;
//
发生的网络事件类型
int iErrorCode[FD_MAX_EVENTS];
//
网络事件对应的错误代码
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
好比当发生 FD_READ 事件时, 那么 networkEvent.lNetworkEvents&FD_READ 将为真,同时 networkEvent.iErrorCode[FD_READ_BIT]
标明了此时的错误代码。server
代码示例:(你还须要一个 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(无效参数),检查时才发现的。