Windows的异步通知I/O模型
2019年5月26日
10:51
同步和异步直接百度一下应该还算很容易理解吧,虽然我一开始看这个同步和异步的时候也是疑惑了一下,觉得名字起的好奇怪啊。但是现在来看的话,名字起的还是意外的形象呢?有点迷。
- 同步方法:调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
- 异步方法:调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而,异步方法通常会在另外一个线程中,"真实"地执行着。整个过程,不会阻碍调用者的工作。
来自 <https://www.cnblogs.com/anny0404/p/5691379.html>
要是让我来打一个比方的话,就是比如有一条cpu的时间线(单核cpu的时间线当然是只有一条的),然后其他的进程或者线程就是一条一条的虚线。他们再一起可以重合成为cpu的时间线,当然他们自己本身是断断续续的。然而我们如果将他们缩放看来的话,会不会发现虚线之间空白的部分会越来越小,直至消失不见。这就好像多出了几个和cpu的时间线完全平行的时间线了。(对于单核cpu)
例如我们一开始的read(recv),write(send)都是同步方式的IO函数。select也是同步函数。
理解和实现异步通知I/O模型
异步通知I/O模型的实现方法有2种:WSAEventSelect函数,WSAAsyncSelect函数
其中WSAAsyncSelect函数需要指定Windows句柄以获取发生的事件(UI相关内容)
告知I/O状态变化的操作就是"通知"。I/O的状态变化可以分为不同的情况:
- 套接字的状态变化:套接字的I/O状态变化
- 发生套接字相关事件:发生套接字IO相关事件
指定某一套接字为事件监视对象:
#include <winsock2.h>
int WSAEventSelect(SOCKET s, WSAEVENT hEventObject,
long lNetworkEvents);
->>成功时返回0,失败时返回SOCKET_ERROR
s:套接字句柄
hEventObject: 事件对象句柄
lNetworkEvents:希望监视的事件类型信息
该函数以异步方式工作。只要s套接字种发生了lNetworkEvents种所指定的事件之一。WSAEventsSelect的异步线程就将hEventObject句柄所指向的内核对象更改为signaled状态。所以这个函数也称为"连接事件对象和套接字的函数"。
第三个参数的事件类型信息,可以通过位或方式同时指定多个信息:
- FD_READ:是否存在需要读取的数据
- FD_WRITE:能否以非阻塞方式传输数据
- FD_OOB:是否收到带外数据
- FD_ACCEPT:是否有新的连接请求
- FD_CLOSE:是否有断开连接的请求
WSAEventSelect函数每次只能传递一个套接字的监视信息。但是于select函数相比的是,通过WSAEventSelect函数传递的套接字信息以注册到操作系统,所以无需再次调用。
manual-reset模式事件对象的其他创建方法
使用WSACreateEvent函数直接创建manual-reset模式non-signaled状态的事件对象
#include <winsock2.h>
WSAEVENT WSACreateEvent(void);
->>成功是返回事件对象句柄,失败时返回WSA_INVALID_EVENT
WSAEVENT的定义如下:
#define WSAEVENT HANDLE
销毁WSACreateEvent函数创建的事件对象
#include <winsock2.h>
BOOL WSACloseEvent(WSAEVENT hEvent);
->> 成功时返回TRUE,失败时返回FALSE
虽然直接使用CloseHandle也可以
验证是否发生事件
虽然使用WaitForMultipleEvents也是可以验证socket是否发生事件
#include <winsock2.h>
DWORD WSAWaitForMultipleEvents(DWORD cEvents,
const WSAEVENT * lphEvents);
->>成功时返回发生事件的对象组的最小索引,失败返回WSA_INVALID_EVENT,等待超时返回WSA_WAIT_TIMEOUT
cEvents:需要验证的事件对象的个数
lphEvents:事件对象句柄数组地址
fWaitAll:是否等待全部,TRUE是,FALSE否
dwTimeout:指定超时事件,以毫秒为单位。WSA_INFINITE阻塞
fAlertable:传递TRUE进入alertable_wait(可警告等待状态)
返回值:返回值减去WSA_WAIT_EVENT_0为发生事件的对象组的最小索引(如果有多个的话)
WSA_MAXIMUM_WAIT_EVENTS指定了WSAWaitForMultipleEvents函数同时可以监视的最大事件对象数。为64
posInfo = WSAWaitForMultipleEvents(numOfSock, hEventArray, FALSE, WSA_INFINITE, FALSE);
startIdx = posInfo - WSA_WAIT_EVENT_0;
…
for(I = startIdx; I < numOfSock; i++)
{
int sigEventIdx = WSAWaitForMultipleEvents(1, &hEventArray[i], TRUE, 0, FALSE);
…
}
区分事件类型
#include <winsock2.h>
int WSAEnumNetworkEvents(SOCKET s, WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents);
->>成功时返回0,失败时返回SOCKET_ERROR
lpNetworkEvents:保存发生的事件类型信息和错误信息的WSANETWORKEVENTS结构体变量地址值
调用该函数以后才会将manual-reset模式的事件对象更改为non-signaled状态,所以之后不必再单独调用ResetEvent函数。
typedef struct _WSANETWORKEVENTS
{
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
}WSANETWORKEVENTS, * LPWSANETWORKEVENTS;
lNetworkEvents保存发生的事件信息,使用位与&运算验证
iErrorCode数组种保存了对应事件的错误信息,使用iErrorCode[FD_XXX_BIT]查看对应事件的错误信息
WSANETWORKEVENTS netEvents;
…
WSAEnumNetworkEvents(hSock, hEvent, &netEvents);
if(netEvents.lNetworkEvents & FD_ACCEPT)
{
// FD_ACCEPT 事件处理
}
if(netEvents.lNetworkEvents & FD_READ)
{
// FD_READ 事件处理
}
if(netEvents.lNetworkEvents & FD_CLOSE)
{
// FD_CLOSE 事件处理
}
同时如果对应事件发生错误,那么iErrorCode[FD_XXX_BIT]中应存储0以外的值
WSANETWORKEVENTS netEvents;
…
WSAEnumNetworkEvents(hSock, hEvent, &netEvents);
…
if(netEvents.iErrorCode[FD_READ_BIT] != 0)
{
// 发生FD_READ事件相关错误
}