WSAEventSelect模式
作者:admin 日期:2012-05-16
WSAEventSelect模型其实很简单,就是将一个事件对象同一个socket绑定并设置要监视的网络事件,当这个socket有我们感兴趣的网络事件到达时,ws2_32.dll就将这个事件对象置为受信状态(signaled),在程序中等待这个事件对象受信后,根据网络事件类型做不同的处理。如果对线程同步机制有些了解的话,这个模型很容易理解,其实就是CreateEvent系列的winsock版。
无代码无真相,具体API的参数含义可以参考MSDN,MSDN上对这个模型解释的非常详尽。
// 使用WSAEventSelect的代码片段,百度贴吧字数限制,略去错误处理及界面操作
// 为了能和多个客户端通信,使用两个数组分别记录所有通信的会话套接字
// 以及和这些套接字绑定的事件对象
// WSA_MAXIMUM_WAIT_EVENTS是系统内部定义的宏,值为64
SOCKET g_sockArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT g_eventArray[WSA_MAXIMUM_WAIT_EVENTS];
// 事件对象计数器
int nEventTotal = 0;
// 创建监听套接字sListenSocket,并对其绑定端口和本机ip 代码省去
........
// 设置sListenSocket为监听状态
listen(sListenSocket, 5);
// 创建事件对象,同CreateEvent一样,event创建后被置为非受信状态
WSAEVENT acceptEvent = WSACreateEvent();
// 将sListenSocket和acceptEvent关联起来
// 并注册程序感兴趣的网络事件FD_ACCEPT 和 FD_CLOSE
// 这里由于是在等待客户端connect,所以FD_ACCEPT和FD_CLOSE是我们关心的
WSAEventSelect(sListenSocket, acceptEvent, FD_ACCEPT|FD_CLOSE);
// 添加到数组中
g_eventArray[nEventTotal] = acceptEvent;
g_sockArray[nEventTotal] = sListenSocket;
nEventTotal++;
// 处理网络事件
while(TRUE)
{
// 由于第三个参数是 FALSE,所以 g_eventArray 数组中有一个元素受信 WSAWaitForMultipleEvents 就返回
// 注意 返回值 nIndex 减去 WSA_WAIT_EVENT_0 的值才是受信事件在数组中的索引。
// 如果有多个事件同时受信,函数返回索引值最小的那个。
// 由于第四个参数指定 WSA_INFINITE ,所以没有对象受信时会无限等待。
int nIndex = WSAWaitForMultipleEvents(nEventTotal, g_eventArray, FALSE, WSA_INFINITE, FALSE);
// 取得受信事件在数组中的位置。
nIndex = nIndex - WSA_WAIT_EVENT_0;
// 判断受信事件 g_eventArray[nIndex] 所关联的套接字 g_sockArray[nIndex] 的网络事件类型
// MSDN中说如果事件对象不是NULL, WSAEnumNetworkEvents 会帮咱重置该事件对象为非受信,方便等待新的网络事件
// 也就是说这里的 g_eventArray[nIndex] 变为非受信了,所以程序中不用再调用 WSAResetEvent了
// WSANETWORKEVENTS 这个结构中 记录了关于g_sockArray[nIndex] 的网络事件和错误码
WSANETWORKEVENTS event;
WSAEnumNetworkEvents(g_sockArray[nIndex], g_eventArray[nIndex], &event);
// 这里处理 FD_ACCEPT 这个网络事件
// event.lNetWorkEvents中记录的是网络事件类型
if(event.lNetworkEvents & FD_ACCEPT)
{
// event.iErrorCode是错误代码数组,event.iErrorCode[FD_ACCEPT_BIT] 为0表示正常
if(event.iErrorCode[FD_ACCEPT_BIT] == 0)
{
// 连接数超过系统约定的范围
if(nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
{
ErrorHandle...
continue;
}
// 没有问题就可以accept了
SOCKET sAcceptSocket = accept(g_sockArray[nIndex], NULL, NULL);
// 新建的会话套接字用于C/S间的数据传输,所以这里关心FD_READ,FD_CLOSE,FD_WRITE三个事件
WSAEVENT event = WSACreateEvent();
WSAEventSelect(sAcceptSocket, event, FD_READ|FD_CLOSE|FD_WRITE);
// 将新建的会话套接字及与该套接字关联的事件对象添加到数组中
g_eventArray[nEventTotal] = event;
g_sockArray[nEventTotal] = sAcceptSocket;
nEventTotal++;
}
//event.iErrorCode[FD_ACCEPT_BIT] != 0 出错了
else
{
ErrorHandle...
break;
}
}
// 这里处理FD_READ通知消息,当会话套接字上有数据到来时,ws2_32.dll会记录该事件
else if(event.lNetworkEvents & FD_READ)
{
if(event.iErrorCode[FD_READ_BIT] == 0)
{
int nRecv = recv(g_sockArray[nIndex], buffer, nbuffersize, 0);
if(nRecv == SOCKET_ERROR)
{
// 为了程序更鲁棒,这里要特别处理一下WSAEWOULDBLOCK这个错误
// MSDN中说在异步模式下有时recv(WSARecv)读取时winsock的缓冲区中没有数据,导致recv立即返回
// 错误码就是 WSAEWOULDBLOCK,但这时程序并没有出问题,在有新的数据到来时recv还是可以读到数据的
// 所以不能仅仅根据recv返回值是SOCKET_ERROR就认为出错从而执行退出操作。
//如果错误码不是WSAEWOULDBLOCK 则表示真的出错了
if(WSAGetLastError() != WSAEWOULDBLOCK)
{
ErrorHandle...
break;
}
}
// 没出任何错误
else
DoSomeThing...
}
// event.iErrorCode[FD_READ_BIT] != 0
else
{
ErrorHandle...
break;
}
}
// 这里处理FD_CLOSE通知消息
// 当连接被关闭时,ws2_32.dll会记录FD_CLOSE事件
else if(event.lNetworkEvents & FD_CLOSE)
{
if(event.iErrorCode[FD_CLOSE_BIT] == 0)
{
closesocket(g_sockArray[nIndex]);
// 将g_sockArray[nIndex]从g_sockArray数组中删除
for(int j=nIndex; j<nEventTotal-1; j++)
g_sockArray[j] = g_sockArray[j+1];
nEventTotal--;
}
// event.iErrorCode[FD_CLOSE_BIT] != 0
else
{
ErrorHandle...
break;
}
}
// 处理FD_WRITE通知消息
// FD_WRITE事件其实就是ws2_32.dll告诉我们winsock的缓冲区已经ok,可以发送数据了
// 同recv一样,send(WSASend)的返回值也要对SOCKET_ERROR特殊判断一下 WSAEWOULDBLOCK
else if(event.lNetworkEvents & FD_WRITE)
{
//关于FD_WRITE的讨论在下面。
}
}
// 如果出错退出循环 则将套接字数组中的套接字与事件对象统统解除关联
// 给WSAEventSelect的最后一个参数传0可以解除g_sockArray[nIndex]和g_eventArray[nIndex]的关联
// 解除关联后,ws2_32.dll将停止记录g_sockArray[nIndex]这个套接字的网络事件
// 退出时还要关闭所有创建的套接字和事件对象
for(int i = 0; i < nEventTotal; i++)
{
WSAEventSelect(g_sockArray[i], g_eventArray[i], 0);
closesocket(g_sockArray[i]);
WSACloseEvent(g_eventArray[i]);
}
nEventTotal = 0;
DoSomethingElse....
评论: 0 | 查看次数: 7299
订阅
Windows7远程桌面无法全屏显示解决办法
FD_WRITE 事件的触发

文章来自:
Tags: