WinSock WSAEventSelect 模型
在前面我们说了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模型的步骤
- 调用WSACreateEvent为每一个SOCKET创建一个等待对象,并与对应的SOCKET形成映射关系
- 调用WSAEventSelect函数将SOCKET于WSAEvent对象进行绑定
- 调用WSAWaitForMultipleEvents 函数对所有SOCKET句柄进行等待
- 当WSAWaitForMultipleEvents 函数返回时利用返回的索引找到对应的WSAEvent对象和SOCKET对象
- 调用WSAEnumNetworkEvents来获取对应的网络事件,根据网络事件来进行对应的收发操作
- 重复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 模型的更多相关文章
- WinSock WSAEventSelect 模型总结
前言 本文配套代码:https://github.com/TTGuoying/WSAEventSelect-model 由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理 ...
- winsock编程WSAEventSelect模型
winsock编程WSAEventSelect模型 WSAEventSelect模型和WSAAsyncSelec模型类似,都是用调用WSAXXXXXSelec函数将socket和事件关联并注册到系统, ...
- WSAEventSelect模型详解
WSAEventSelect 是 WinSock 提供的一种异步事件通知I/O模型,与 WSAAsyncSelect模型有些类似. 该模型同样是接收 FD_XXX 之类的网络事件,但是是通 ...
- WinSock IOCP 模型总结(附一个带缓存池的IOCP类)
前言 本文配套代码:https://github.com/TTGuoying/IOCPServer 由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理,并且也熟练的掌握了 ...
- WSAEventSelect模型编程 详解
转自:http://blog.csdn.net/wangjieest/article/details/7042108 WSAEventSelect模型编程 WSAEventSelect模型编程这个模型 ...
- WSAEventSelect模型
WSAEventSelect模型 EventSelect WSAEventSelect function The WSAEventSelect function specifies an event ...
- WinSock IO模型 -- WSAEventSelect模型事件触发条件说明
FD_READ事件 l 调用WSAEventSelect函数时,如果当前有数据可读 l 有数据到达时,并且没有发送过FD_READ事件 l 调用recv/recvfrom函数后,仍然有数据可读时 ...
- Winsock IO模型之select模型
之所以称其为select模型是因为它主要是使用select函数来管理I/O的.这个模型的设计源于UNIX系统,目的是允许那些想要避免在套接字调用上阻塞的应用程序有能力管理多个套接字. int sele ...
- 三.Windows I/O模型之事件选择(WSAEventSelect )模型
1.事件选择模型:和异步选择模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知.对于异步选择模型采用的网络事件来说,它们均可原封不动地移植到事件选择模型.事件选择模型和 ...
随机推荐
- CPU 的工作原理
内部架构 CPU 的根本任务就是执行指令,对计算机来说最终都是一串由 0 和 1 组成的序列.CPU 从逻辑上可以划分成 3 个模块,分别是控制单元.运算单元和存储单元 .其内部架构如下: [1]控制 ...
- Centos查看端口占用令
Centos查看端口占用情况命令,比如查看80端口占用情况使用如下命令: lsof -i tcp:80 列出所有端口 netstat -ntlp 1.开启端口(以80端口为例) 方法一: /sbin/ ...
- Idea中文输入问题2
Desc: win10 idea2017版本+jdk1.9 输入中文无法光标跟随 解决过程: 网上的各种三大类型教程(1)删除jre64(2)删除jre64,然后复制jdk的jre到idea目录,最后 ...
- EasyUI学习笔记(三)—— message和menubutton的使用
一.message(消息框) 1.1 alert <script type="text/javascript"> $(function () { // alert方 ...
- 1017 A除以B (20 分)
#include <iostream> #include <string> using namespace std; int main() { string num; int ...
- ui-grid使用详解
HTML <pre name="code" class="html"><!--ui-grid css--> <link rel=& ...
- 【算法笔记】B1046 划拳
1046 划拳 (15 分) 划拳是古老中国酒文化的一个有趣的组成部分.酒桌上两人划拳的方法为:每人口中喊出一个数字,同时用手比划出一个数字.如果谁比划出的数字正好等于两人喊出的数字之和,谁就赢了,输 ...
- Oracle分析函数系列之first_value/last_value:在记录集中查找第一条记录和最后一条记录
[转自] http://blog.csdn.net/rfb0204421/article/details/7675911 注意:与max,min的区别,虽然也可以实现,但只是针对数字字段. 1.初始化 ...
- Spring中如何向 Bean注入系统属性或环境变量
[转自] http://unmi.cc/spring-injection-system-properties-env/ 在 Spring 中为 javabean 注入属性文件中的属性值一般人都知道的, ...
- 【研究】Tomcat远程代码执行漏洞(CVE-2017-12615)
一.Tomcat远程代码执行漏洞(CVE-2017-12615) 1.1 实验环境 操作机:windows 10 IP:192.168.1. ...