《TCP/IP网络编程》第21章 异步通知I/O模型

92 篇文章 18 订阅
34 篇文章 3 订阅

理解异步通知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
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值