异步io通知 WSAEventSelect

WSAEventSelect 就是 select的增强版;

注意WSAEventSelect 是通知异步, 而不是传送数据异步;

总的来说就是一个异步的阻塞模型;

如果要与 select 做个比较的话 :

select 在 需要进行或者可以进行io处理时 返回. 而WSAEventSelect 在返回时(WSAWaitForMultipleEvents) 与io状态无关;

例如: select 在收到数据 并返回时 , 他监听此套接字并检查接受缓冲区, 等到缓冲区能读时再返回.

而WSAEventSelect 的等待函数 WSAWaitForMultipleEvents 只要此套接字有事件发生就返回;

因此称为异步通知;

另WSAEventSelect 与 nix下的 epoll 整体编程模型很像; epoll : epoll说明
具体使用到的函数:

WSACreateEvent 创建一个事件, 默认手动模式

WSAEventSelect 让一个套接字与一个事件捆绑在一起 注册到操作系统, 无需像select 每次重置;

  • WSAWaitForMultipleEvents (阻塞) 等待套接字对应的事件发生, 最多能监听WSA_MAXIMUM_WAIT_EVENTS 个事件;

    • WSAEnumNetworkEvents 查看套接字对应的具体事件 ; 这也就是与select 返回时机的不同的原因; 注意:此函数将重置事件,

      因此无需调用WSAResetEvent;

重要就这4个函数, 其他的函数调用查看msdn 即可;

echo_serv.c

#include "../utils.h"
#define BUFF_SIZE 8192
 
//关闭套接字后 . 调整数组
static void adjust_sockarr(SOCKET * sock_arr, int start_index, int total){
    for (int i = start_index; i < total; ++i)
        sock_arr[i] = sock_arr[i + 1];
}
 
//关闭事件后, 调整数组
static void adjust_eventarr(WSAEVENT * event_arr, int start_index, int  total){
    for (int i = start_index; i < total; ++i)
        event_arr[i] = event_arr[i + 1];
}
 
 
int _tmain(int argc, _TCHAR* argv[])
{
    unsigned short port = 0;
    scanf(" %hd", &port);
    WSADATA wsadata;
    WSAStartup(MAKEWORD(2,2),&wsadata);
    SOCKET listensock = socket(AF_INET, SOCK_STREAM, 0);
    SOCKADDR_IN serv_addr, cli_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    memset(&cli_addr, 0, sizeof(cli_addr));
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
 
    if (bind(listensock, (SOCKADDR*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR){
        print_error(WSAGetLastError());
        return 0;
    }
 
    if (listen(listensock, 5) == SOCKET_ERROR){
        print_error(WSAGetLastError());
        return 0;
    }
 
    
    //创建一个与监听套接字捆绑的事件
    WSAEVENT wsaevent = WSACreateEvent();
 
    //FD_ACCEPT :一旦连接发生, 此事件将发生
    if (WSAEventSelect(listensock, wsaevent, FD_ACCEPT) == SOCKET_ERROR){
        print_error(WSAGetLastError());
        return 0;
    }
 
    int event_count = 0 , cli_len = sizeof(cli_addr);
 
    //准备2个数组,用于存放套接字与事件
    SOCKET sock_arr[WSA_MAXIMUM_WAIT_EVENTS] = {0};
    WSAEVENT event_arr[WSA_MAXIMUM_WAIT_EVENTS] = {0};
 
    //把套接字与事件存放在数组里 , 并一一对应
    sock_arr[event_count] = listensock;
    event_arr[event_count] = wsaevent;
    ++event_count;  // 事件总数 == 套接字总数
 
 
    DWORD pos , start_index ,event_index , strlen;
    WSANETWORKEVENTS netevents;
    SOCKET cli_socket;
    char buf[BUFF_SIZE];
 
    
    while (1){
 
        //等待事件发生,只要一个事件发生就返回, 具体参数查看msdn;
        pos = WSAWaitForMultipleEvents(event_count, event_arr, FALSE, WSA_INFINITE, FALSE);
        printf("pos:%d , event_count:%d\n", pos,event_count);
 
        start_index = pos - WSA_WAIT_EVENT_0;
 
        //循环的意义:在一个繁忙的服务器上有可能在一个事件发生的瞬间,又有一个事件发生了
 
        for (int i = start_index; i < event_count; ++i){
 
            //依次验证从这个已经返回的事件,以及之后的套接字事件有没有发生.
            //由于timeout参数是 0 ,所以非阻塞
            event_index = WSAWaitForMultipleEvents(1, event_arr+i, TRUE, 0, FALSE);
 
            if (WSA_WAIT_FAILED == event_index || WSA_WAIT_TIMEOUT == event_index){
                printf("index:%d , timeout or failed\n", i);
                continue;
            }
 
            //有事件发生了,查看套接字对应的具体事件是什么
            if (WSAEnumNetworkEvents(sock_arr[i], event_arr[i], &netevents) == SOCKET_ERROR){
                _tprintf(TEXT("WSAEnumNetworkEvents error : index:%d \ndetail:"), i);
                print_error(WSAGetLastError());
                continue;
            }
 
            
            strlen = sprintf(buf, "sock_arr[%d] occurs ", i);
            buf[strlen] = 0;
            if (netevents.lNetworkEvents & FD_CONNECT)
                strcat(buf, " connect ");
            if (netevents.lNetworkEvents & FD_ACCEPT)
                strcat(buf , " accept ");
            if (netevents.lNetworkEvents & FD_WRITE)
                strcat(buf , " write ");
            if (netevents.lNetworkEvents & FD_CLOSE)
                strcat(buf , " close ");
            puts(buf);
 
 
            //如果有连接
            if (netevents.lNetworkEvents & FD_ACCEPT){
                //如果有错
                if (netevents.iErrorCode[FD_ACCEPT_BIT] != 0){
                    _tprintf(TEXT("accept error , index:%d,detail:\n"),i);
                    print_error(WSAGetLastError());
                    continue;
                }
                cli_len = sizeof(cli_addr);
                
                cli_socket = accept(sock_arr[i], (SOCKADDR*)&cli_addr, &cli_len);
 
                //给新的连接创建事件
                wsaevent = WSACreateEvent();
                //把套接子与事件注册进操作系统,用于监听
                if (WSAEventSelect(cli_socket, wsaevent, FD_READ | FD_CLOSE) != SOCKET_ERROR){
                    //放进数组
                    sock_arr[event_count] = cli_socket;
                    event_arr[event_count] = wsaevent;
                    ++event_count;
                    printf("new client ip:%s, port:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
                }
            }
 
            //如果套接字能读
            if (netevents.lNetworkEvents & FD_READ){
                if (netevents.iErrorCode[FD_READ_BIT] != 0){
                    _tprintf(TEXT("read error , index:%d detail:\n"), i);
                    print_error(WSAGetLastError());
                    continue;
                }
                strlen = recv(sock_arr[i], buf, BUFF_SIZE, 0);
                buf[strlen] = 0;
                strlen = send(sock_arr[i], buf, strlen, 0);
                printf("recv len : %d, buf:%s\n", strlen, buf);
            }
 
            //如果对端关闭了
            if (netevents.lNetworkEvents & FD_CLOSE){
                //关闭事件
                WSACloseEvent(event_arr[i]);
                closesocket(sock_arr[i]);
                --event_count;
 
                //调整数组
                adjust_sockarr(sock_arr, i, event_count);
                adjust_eventarr(event_arr, i, event_count);
                if (netevents.iErrorCode[FD_CLOSE_BIT] != 0){
                    _tprintf(TEXT("close error , index:%d , error:%d .detail:\n"), i, netevents.iErrorCode[FD_CLOSE_BIT]);
                    print_error(WSAGetLastError());
                    continue;
                }
                cli_len = sizeof(cli_addr);
                if (getpeername(sock_arr[i], (SOCKADDR*)&cli_addr, &cli_len) != SOCKET_ERROR){
                    printf("peer ip:%s, port:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
                }
                printf("sock_arr[%d] closed\n", i);
            }
        }
    }
 
    WSACleanup();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值