在前面我们说了WSAAsyncSelect 模型,它相比于select模型来说提供了这样一种机制:当发生对应的IO通知时会立即通知操作系统,并调用对应的处理函数,它解决了调用send和 recv的时机问题,但是它有一个明显的缺点,就是它必须依赖窗口。对此WinSock 提供了另一种模型 WSAEventSelect

模型简介

该模型主要特色在于它使用事件句柄来完成SOCKET事件的通知。与WSAAsyncSelect 模型类似,它也允许使用事件对象来完成多个socket的完成通知。

该模型首先在每个socket句柄上调用WSACreateEvent来创建一个WSAEvent对象句柄(早期的WSAEvent与传统的Event句柄有一定的区别,但是从WinSock2.0 以后二者是同一个东西)。接着调用WSAEventSelect将SOCKET句柄和WSAEvent对象绑定,最终通过WSAWaitForMultiEvents来等待WSAEvent变为有信号,然后再来处理对应的socket

WSAEvent有两种工作模式和工作状态

工作状态有有信号和无信号两种

工作模式有手工重置和人工重置,手工重置指的是每当WSAWaitForMultiEvents或者WSAWaitForSingleEvents 返回之后,WSAEvent不会自动变为无信号,需要手工调用WSAResetEvent来将WSAEvent对象设置为无信号,而自动重置表示每次等待函数返回后会自动重置为无信号;调用WSACreateEvent创建的WSAEvent对象是需要手工重置的,如果想创建自动重置的WSAEvent对象可以调用CreateEvent函数来创建(由于WinSock2.0 之后二者没有任何区别,所以只需要调用CreateEvent并将返回值强转为WSAEvent即可)

WSAEventSelect函数的原型如下:

int WSAEventSelect(  SOCKET s,  WSAEVENT hEventObject,  long lNetworkEvents);

其中s表示对应的SOCKET,hEventObject表示对应的WSAEvent对象,lNetworkEvents 表示我们需要处理哪些事件,它有一些对应的宏定义

网络事件 对应的含义
FD_READ 当前可以进行数据接收操作,此时可以调用像 recv, recvfrom, WSARecv, 或者 WSARecvFrom 这样的函数
FD_WRITE 此时可以发送数据,可以调用 send, sendto, WSASend, or WSASendTo
FD_ACCEPT 可以调用accept (Windows Sockets) 或者 WSAAccept 除非返回的错误代码是WSATRY_AGAIN.
FD_CONNECT 表示当前可以连接远程服务器
FD_CLOSE 当前收到关闭的消息

当WSAWaitForMultipleEvents返回时同时会返回一个序号,用于标识是数组中的哪个WSAEvent有信号,我们使用 index - WSA_WAIT_EVENT_0 来获取对应WSAEvent在数组中的下标,然后根据这个事件对象找到对应的SOCKET即可

获得了对应的SOCKET以后,还需要获取到当前是哪个事件发生导致它变为有信号,我们可以调用WSAEnumNetworkEvents函数来获取对应发生的网络事件

int WSAEnumNetworkEvents(
SOCKET s,
WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents
);

s就是要获取其具体事件通知的SOCKET句柄

hEventObject就是对应的WSAEvent句柄,可以不传入,因为SOCKET句柄已经说明了要获取那个句柄上的通知,当然如果传入了,那么这个函数会对这个WSAEvent做一次重置,置为无信号的状态,相当于WSAResetEvent调用。此时我们就不需要调用WSAResetEvent函数了

最后一个参数是一个结构,结构的定义如下:

typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;

第一个数据是当前产生的网络事件。

iErrorCode数组是对应每个网络事件可能发生的错误代码,对于每个事件错误代码其具体数组下标是预定义的一组FD_开头的串再加上一个_BIT结尾的宏,比如FD_READ事件对应的错误码下标是FD_READ_BIT

下面的代码演示了处理接收(读取)数据的事件错误的例子代码

if (NetworkEvents.lNetworkEvents & FD_READ)
{
if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
{
printf("FD_READ failed with error %d\n",
NetworkEvents.iErrorCode[FD_READ_BIT]);
}
}

到目前为止,我们可以总结一下使用WSAEventSelect模型的步骤

  1. 调用WSACreateEvent为每一个SOCKET创建一个等待对象,并与对应的SOCKET形成映射关系
  2. 调用WSAEventSelect函数将SOCKET于WSAEvent对象进行绑定
  3. 调用WSAWaitForMultipleEvents 函数对所有SOCKET句柄进行等待
  4. 当WSAWaitForMultipleEvents 函数返回时利用返回的索引找到对应的WSAEvent对象和SOCKET对象
  5. 调用WSAEnumNetworkEvents来获取对应的网络事件,根据网络事件来进行对应的收发操作
  6. 重复3~5的步骤

示例

下面是一个简单的例子

int _tmain(int argc, TCHAR *argv[])
{
WSADATA wd = {0};
WSAStartup(MAKEWORD(2, 2), &wd); SOCKET skServer = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
SOCKADDR_IN AddrServer = {AF_INET};
AddrServer.sin_port = htons(SERVER_PORT);
AddrServer.sin_addr.s_addr = htonl(INADDR_ANY);
bind(skServer, (SOCKADDR*)&AddrServer, sizeof(SOCKADDR));
listen(skServer, 5);
printf("服务端正在监听...........\n"); CWSAEvent WSAEvent;
WSAEvent.InsertClient(skServer, FD_ACCEPT | FD_CLOSE);
WSAEvent.EventLoop(); WSACleanup();
return 0;
}

在代码中定义了一个类CWSAEvent,该类封装了关于该模型的相关操作和对应事件对象和SOCKET对象的操作,在主函数中首先创建监听的SOCKET,然后绑定、监听,并提交监听SOCKET到类中,以便对它进行管理,函数InsertClient的定义如下:

void CWSAEvent::InsertClient(SOCKET skClient, long lNetworkEvents)
{
m_socketArray[m_nTotalItem] = skClient;
m_EventArray[m_nTotalItem] = WSACreateEvent();
WSAEventSelect(skClient, m_EventArray[m_nTotalItem++], lNetworkEvents);
}

这个函数中主要向事件数组和SOCKET数组的对应位置添加了相应的成员,然后调用WSAEventSelect。

而类的EventLoop函数定义了一个循环来重复前面的3~5步,函数的部分代码如下:

int CWSAEvent::WaitForAllClient()
{
DWORD dwRet = WSAWaitForMultipleEvents(m_nTotalItem, m_EventArray, FALSE, WSA_INFINITE, FALSE);
WSAResetEvent(m_EventArray[dwRet - WSA_WAIT_EVENT_0]);
return dwRet - WSA_WAIT_EVENT_0;
} int CWSAEvent::EventLoop()
{
WSANETWORKEVENTS wne = {0};
while (TRUE)
{
int nRet = WaitForAllClient();
WSAEnumNetworkEvents(m_socketArray[nRet], m_EventArray[nRet], &wne);
if (wne.lNetworkEvents & FD_ACCEPT)
{
if (0 != wne.iErrorCode[FD_ACCEPT_BIT])
{
OnAcceptError(nRet, m_socketArray[nRet], wne.iErrorCode[FD_ACCEPT_BIT]);
}else
{
OnAcccept(nRet, m_socketArray[nRet]);
}
}else if (wne.lNetworkEvents & FD_CLOSE)
{
if (0 != wne.iErrorCode[FD_CLOSE_BIT])
{
OnCloseError(nRet, m_socketArray[nRet], wne.iErrorCode[FD_CLOSE_BIT]);
}else
{
OnClose(nRet, m_socketArray[nRet]);
}
}else if (wne.lNetworkEvents & FD_READ)
{
if (0 != wne.iErrorCode[FD_READ_BIT])
{
OnReadError(nRet, m_socketArray[nRet], wne.iErrorCode[FD_READ_BIT]);
}else
{
OnRead(nRet, m_socketArray[nRet]);
}
}else if (wne.lNetworkEvents & FD_WRITE)
{
if (0 != wne.iErrorCode[FD_WRITE_BIT])
{
OnWriteError(nRet, m_socketArray[nRet], wne.iErrorCode[FD_WRITE_BIT]);
}else
{
OnWrite(nRet, m_socketArray[nRet]);
}
}
}
}

函数首先进行了等待,当等待函数返回时,获取对应的下标,以此来获取到socket和事件对象,然后调用WSAEnumNetworkEvents来获取对应的网络事件,最后根据事件调用不同的处理函数来处理

在上面的代码中,这个循环有一个潜在的问题,我们来设想这么一个场景,当有多个客户端同时连接服务器,在第一次等待返回时,我们主要精力在进行该IO事件的处理,也就是响应这个客户端A的请求,而此时客户端A又发送了一个请求,而另外几个客户端B随后也发送了一个请求,在第一次处理完成后,等待得到的将又是客户端A,而后续客户端B的请求又被排到了后面,如果这个客户端A一直不停的发送请求,可能造成的问题是服务器一直响应A的请求,而对于B来说,它的请求迟迟得不到响应。为了避免这个问题,我们可以在函数WSAWaitForMultipleEvents 返回后,针对数组中的每个SOCKET循环调用WSAWaitForMultipleEvents将等待的数量设置为1,并将超时值设置为0,这个时候这个函数的作用就相当于查看数组中的每个SOCKET,看看是不是有待决的,当所有遍历完成后依次处理这些请求或者专门创建对应的线程来处理请求

最后,整个示例代码


WinSock WSAEventSelect 模型的更多相关文章

  1. WinSock WSAEventSelect 模型总结

    前言 本文配套代码:https://github.com/TTGuoying/WSAEventSelect-model 由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理 ...

  2. winsock编程WSAEventSelect模型

    winsock编程WSAEventSelect模型 WSAEventSelect模型和WSAAsyncSelec模型类似,都是用调用WSAXXXXXSelec函数将socket和事件关联并注册到系统, ...

  3. WSAEventSelect模型详解

    WSAEventSelect 是 WinSock 提供的一种异步事件通知I/O模型,与 WSAAsyncSelect模型有些类似.       该模型同样是接收 FD_XXX 之类的网络事件,但是是通 ...

  4. WinSock IOCP 模型总结(附一个带缓存池的IOCP类)

    前言 本文配套代码:https://github.com/TTGuoying/IOCPServer 由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理,并且也熟练的掌握了 ...

  5. WSAEventSelect模型编程 详解

    转自:http://blog.csdn.net/wangjieest/article/details/7042108 WSAEventSelect模型编程 WSAEventSelect模型编程这个模型 ...

  6. WSAEventSelect模型

    WSAEventSelect模型 EventSelect WSAEventSelect function The WSAEventSelect function specifies an event ...

  7. WinSock IO模型 -- WSAEventSelect模型事件触发条件说明

    FD_READ事件 l  调用WSAEventSelect函数时,如果当前有数据可读 l  有数据到达时,并且没有发送过FD_READ事件 l  调用recv/recvfrom函数后,仍然有数据可读时 ...

  8. Winsock IO模型之select模型

    之所以称其为select模型是因为它主要是使用select函数来管理I/O的.这个模型的设计源于UNIX系统,目的是允许那些想要避免在套接字调用上阻塞的应用程序有能力管理多个套接字. int sele ...

  9. 三.Windows I/O模型之事件选择(WSAEventSelect )模型

    1.事件选择模型:和异步选择模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知.对于异步选择模型采用的网络事件来说,它们均可原封不动地移植到事件选择模型.事件选择模型和 ...

随机推荐

  1. 关于fatal error LINK1123:failure during conversion to COFF:file invalid or corrupt

    今天用Visual Studio 2010编译postgresql工程时突然遇到下面这个编译错误: fatal error LINK1123:failure during conversion to ...

  2. C#/ASP.NET对URL中的中文乱码处理

    前言:UTF-8中,一个汉字对应三个字节,GB2312中一个汉字占用两个字节. 不论何种编码,字母数字都不编码,特殊符号编码后占用一个字节. 1.直接在C#后台编码URL参数 引用类库:System. ...

  3. ubuntu 18.04 通过联网方式安装wine

    ubuntu 18.04 通过联网方式安装wine 1.如果是64位机器,先开启允许32位架构程序运行 sudo dpkg --add-architecture i386 2.添加元wine源码安装仓 ...

  4. 关于运行robot framework 报错解决方法,ModuleNotFoundError: No module named 'robot'

    报错: command: pybot.bat --argumentfile c:\users\76776\appdata\local\temp\RIDEiw0utf.d\argfile.txt --l ...

  5. selenium定位不到元素

    selenium定位不到元素时,网上大部分查到都是iFrame的切换问题,然后是多窗口.句柄的处理问题, 在初学是遇到定位不到元素,一直在找上面的问题,发现都不是上面的问题, 后来才发现是页面刷新的问 ...

  6. POJ:2456 Aggressive cows(z最大化最小值)

    描述 农夫 John 建造了一座很长的畜栏,它包括N (2 <= N <= 100,000)个隔间,这些小隔间依次编号为x1,...,xN (0 <= xi <= 1,000, ...

  7. SGU - 495 概率DP

    题意:n个带礼物的盒子和m个人,每个人拿一个盒子并放回,如果里面有礼物就拿走(盒子还是留下),问m个人带走礼物的期望 #include<iostream> #include<algo ...

  8. java的长字符串转化为短字符串

    public class CustomEncrypt{ public static void main( String[] args ) { /* * c#给的正确测试用例: id=>mid * ...

  9. Java打包成jar

    若要生成一个名为 cal.jar 的可执行jar文件:(文件名可以是任意合法名字)  (这是我认为简单实用的一种方法,还有很多别的方法在此就不介绍了)  第一 把程序生成的所有字节码文件(即.clas ...

  10. ubuntu 登陆闪回

    问题: Ubuntu18.04 不能进入系统了,在登陆界面输入密码后,就闪回: 解决: ssh登陆机机器: 查看用户目录下的,文件权限: .Xauthority 如果是root用户,则更改用户 sud ...