《TCP/IP网络编程》第21章 异步通知I/O模型
理解异步通知I/O模型
同步和异步
同步的关键是函数的调用及返回时刻,以及数据传输的开始和完成时刻。
- 调用send函数的瞬间开始传输数据,send函数执行完(返回)的时刻完成数据传输(数据完全传输到输出缓冲,操作系统完成网络传输)。
- 调用recv函数的瞬间开始接收数据,recv函数执行完(返回)的时刻完成数据接收。
异步(Asynchronous),不一致。
异步I/O指I/O函数的返回时刻与数据收发的完成时刻不一致。
同步I/O缺点及异步解决方案
同步I/O的过程中函数无法返回,不能执行其他任务。
异步I/O立即返回函数,可以执行其他任务,更有效地使用CPU。
异步通知I/O模型
异步工作方式“通知I/O”。
通知I/O:通知输入缓冲收到数据并需要读取,以及输出缓冲为空故可以发送数据。
select是同步方式通知I/O,需要I/O或可以进行I/O的时间点(I/O相关事件发生的时间点)与select函数的返回时间点一致。
异步通知I/O中,指定I/O监视对象的函数和实际验证状态变化的函数是相互分离的。指定监视对象后可以离开执行其他任务,最后再回来验证状态变化。
实现异步通知I/O模型
WSAAsyncSelect函数,需指定Windows句柄以获取发生的事件(UI相关内容),本书不会涉及。
WSAEventSelect函数
I/O的状态变化
- 套接字的I/O状态变化
- 发生套接字I/O相关事件
#include <winsock2.h>
//成功0,失败SOCKET_ERROR
int WSAEventSelect(
SOCKET s, //监视对象的套接字句柄
WSAEVENT hEventObject, //事件对象句柄
long lNetworkEvents //监视的事件类型信息,或运算
);
传入参数s的套接字内只要发生lNetworkEvents中指定事件之一, WSAEventSelect函数就将hEventObject句柄所指内核对象改为signaled状态。该函数是连接事件对象和套接字的函数,以异步通知方式工作,调用后直接返回。无需对已注册的套接字再次调用WSAEventSelect函数,套接字信息已注册到操作系统中。
事件类型信息:
- FD_READ:可读
- FD_WRITE:可写
- FD_OOB:收到带外数据
- FD_ACCEPT:新连接
- FD_CLOSE:断开连接
manual-reset模式事件对象的创建
CreateEvent函数可以创建auto-reset或manual-reset模式事件对象。
WSACreateEvent只创建manual-reset模式non-signaled状态的事件对象。
#include <winsock2.h>
//#define WSAEVENT HANDLE
//失败WSA_INVALID_EVENT
WSAEVENT WSACreateEvent(void);
//销毁上述函数创建的事件对象
BOOL WSACloseEvent(WSAEVENT hEvent);
验证是否发生了事件
#include <winsock2.h>
//失败WSA_INVALID_EVENT
DWORD WSAWaitForMultipleEvents(
DWORD cEvents,//验证是否转为signaled状态的事件对象个数
const WSAEVENT *lphEvents,//事件对象句柄数组
BOOL fWaitAll,//TRUE,所有事件对象signaled状态才返回;FALSE,任一事件对象signaled状态就返回
DWORD dwTimeout,//超时(1/1000秒),WSA_INFINITE一直等待
BOOL fAlertable//TRUE时进入alertable wait(可警告等待)状态
);
//返回值减去WSA_WAIT_EVENT_0,得到转为signaled状态事件对象句柄对应索引(事件对象句柄数组)。
//多个signaled状态事件对象,得到较小值(调用一次只能获取一个signaled状态事件对象句柄)。
//超时返回WAIT_TIMEOUT
最多可传递64个事件对象,如需监视更多句柄,只能创建线程或扩展保存句柄的数组,并多次调用上述函数。
获取所有signaled状态的事件对象。
int posInfo = WSAWaitForMultipleEvents(nmuOfSock, hEventArray, FALSE, WSA_INFINITE, FALSE);
int startIdx = posInfo-WSA_WAIT_EVENT_0;
for(int i=startIdx; i<numOfSock, i++) {
int sigEventIdx = WSAWaitForMultipleEvents(1, &hEventArray[i], TRUE, 0, FALSE);
}
区分事件类型
#include <winsock2.h>
//成功0,失败SOCKET_ERROR
int WSAEnumNetworkEvents(
SOCKET s,//发生事件的套接字句柄
WSAEVENT hEventObject, //与套接字关联的signaled状态的事件对象句柄
LPWSANETWORKEVENTS lpNetworkEvents//保存发生的事件类型信息和错误信息
);
上述函数将manual-reset模式事件对象改为non-signaled状态,无需单独调用ResetEvent函数。
type struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
查看发生的事件类型
WSANETWORKEVENTS netEvents;
WSAEnumNetworkEvents(hSock, hEvent, &netEvents);
if(netEvents.lNetworkEvents & FD_ACCEPT) {
//
}
if(netEvents.lNetworkEvents & FD_READ) {
//
}
if(netEvents.lNetworkEvents & FD_WRITE) {
//
}
if(netEvents.lNetworkEvents & FD_CLOSE) {
//
}
iErrorCode数组保存错误信息。
- FD_READ错误,查看iErrorCode[FD_READ_BIT]
- FD_WRITE错误,查看iErrorCode[FD_WRITE_BIT]
- FD_XXX错误,查看iErrorCode[FD_XXX_BIT]
WSANETWORKEVENTS netEvents;
WSAEnumNetworkEvents(hSock, hEvent, &netEvents);
if(netEvents.iErrorCode[FD_READ_BIT] != 0) {
//
}
异步通知I/O模型回声服务器
21.AsyncNotiEchoServ_win.c
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#define PORT 9999
#define BUF_SIZE 100
void ErrorHanding(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
void CompressSockets(SOCKET hSockArr[], int idx, int total)
{
for (int i = idx; i < total; i++)
hSockArr[i] = hSockArr[i + 1];
}
void CompressEvents(WSAEVENT hEventArr[], int idx, int total)
{
for (int i = idx; i < total; i++)
hEventArr[i] = hEventArr[i + 1];
}
int main(int argc, char *argv[])
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHanding("WSAStartup() error!");
SOCKET serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == INVALID_SOCKET)
ErrorHanding("socket() error!");
int opt = 1;
if (setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) < 0)
ErrorHanding("setsockopt() error!");
int addr_size = sizeof(SOCKADDR_IN);
SOCKADDR_IN serv_addr;
memset(&serv_addr, 0, addr_size);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(PORT);
if (bind(serv_sock, (SOCKADDR *)&serv_addr, addr_size) == SOCKET_ERROR)
ErrorHanding("bind() error!");
if (listen(serv_sock, 5) == SOCKET_ERROR)
ErrorHanding("listen() error!");
WSAEVENT newEvent = WSACreateEvent();
if (WSAEventSelect(serv_sock, newEvent, FD_ACCEPT) == SOCKET_ERROR) // 连接
ErrorHanding("WSAEventSelect() error!");
// 保存通过WSAEventSelect函数连接的套接字和事件对象
SOCKET hSockArr[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT hEventArr[WSA_MAXIMUM_WAIT_EVENTS];
int numOfClntSock = 0;
hSockArr[numOfClntSock] = serv_sock;
hEventArr[numOfClntSock] = newEvent;
numOfClntSock++;
while (1)
{
// 等待事件对象句柄hEventArr转化为signaled状态
// 套接字集合中至少一个套接字发生了事件
int posInfo = WSAWaitForMultipleEvents(numOfClntSock, hEventArr, FALSE, WSA_INFINITE, FALSE);
int startIdx = posInfo - WSA_WAIT_EVENT_0; // 最小起始索引
// 遍历事件对象
for (int i = startIdx; i < numOfClntSock; i++)
{
// 非阻塞获取事件对象状态
int sigEventIdx = WSAWaitForMultipleEvents(1, &hEventArr[i], TRUE, 0, FALSE);
if (sigEventIdx == WSA_WAIT_FAILED || sigEventIdx == WSA_WAIT_TIMEOUT)
{
continue;
}
else
{
// 获取事件类型
sigEventIdx = i;
WSANETWORKEVENTS netEvents;
WSAEnumNetworkEvents(hSockArr[sigEventIdx], hEventArr[sigEventIdx], &netEvents);
// 判断发生的事件
if (netEvents.lNetworkEvents & FD_ACCEPT)
{
if (netEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
{
puts("ACCEPT Error");
break;
}
// 新连接
SOCKADDR_IN clnt_addr;
SOCKET clnt_sock = accept(hSockArr[sigEventIdx], (SOCKADDR *)&clnt_addr, &addr_size);
newEvent = WSACreateEvent();
WSAEventSelect(clnt_sock, newEvent, FD_READ | FD_CLOSE); // 读取和关闭
hSockArr[numOfClntSock] = clnt_sock;
hEventArr[numOfClntSock] = newEvent;
numOfClntSock++;
puts("connected new client...");
}
if (netEvents.lNetworkEvents & FD_READ)
{
if (netEvents.iErrorCode[FD_READ_BIT] != 0)
{
puts("READ Error");
break;
}
// 缓存区间读取数据,并发送
char msg[BUF_SIZE];
/*
int strLen = recv(hSockArr[sigEventIdx], msg, sizeof(msg), 0);
send(hSockArr[sigEventIdx], msg, strLen, 0);
*/
int strLen;
do
{
strLen = recv(hSockArr[sigEventIdx], msg, sizeof(msg), 0);
if (strLen > 0)
send(hSockArr[sigEventIdx], msg, strLen, 0);
} while (strLen == BUF_SIZE);
}
if (netEvents.lNetworkEvents & FD_CLOSE)
{
if (netEvents.iErrorCode[FD_CLOSE_BIT] != 0)
{
puts("CLOSE Error");
break;
}
// 释放事件对象句柄,关闭套接字
WSACloseEvent(hEventArr[sigEventIdx]);
closesocket(hSockArr[sigEventIdx]);
numOfClntSock--;
CompressSockets(hSockArr, sigEventIdx, numOfClntSock);
CompressEvents(hEventArr, sigEventIdx, numOfClntSock);
}
}
}
}
closesocket(serv_sock);
WSACleanup();
return 0;
}
// gcc 21.AsyncNotiEchoServ_win.c -o 21.AsyncNotiEchoServ_win -lws2_32 && 21.AsyncNotiEchoServ_win