详解WSAEventSelect网络模型

初步认识事件选择模型

select网络模型与WSAEventSelect网络模型的差别:
select网络模型是I/O复用模型,占用的是程序自身的时间片。WSAEventSelect网络模型是以事件驱动的网络异步I/O模型,检测数据准备好的事情交由操作系统完成,不占用程序自己的时间片。
(I/O复用与I/O异步的区别?复用指的是,select使用自身时间片开启一个线程来检测多个套接字的信号,同步阻塞本来需要多个线程,现在只需要一个线程,这就是复用;而异步指的是操作系统帮我们完成检测数据到来的任务,程序不用管这个过程,也就不浪费程序自身的时间片做事,当我们接收到有信号的时候,直接去recv就可以了)
异步I/O
select网络模型与WSAEventSelect网络模型的相同点:
1、 select和WSAEventSelect中的WSAWaitForMultipleEvents一次都最多只能检测64个客户端
2、 优化的都是recv等待数据到来的事件,从内核拷贝到程序缓冲区都需要占用自己程序的时间片来完成。

API详解

WSAEVENT  WSACreateEvent (void);

Call this function to create an event object that is manually reset. This function creates the event object with an initial nonsignaled state.
功能:创建的一个手工重置的事件对象,并且初始化为无信号状态。
那么问题来了,我们在线程函数里面并没有使用WSAResetEvent,相应的事件对象会不会一直处于有信号状态?但是我们实际运行中并没有出现事件信号一直存在的情况,这让我困惑好久,在WSAEnumNetworkEvents给出答案
返回值:失败返回NULL,成功返回一个网络事件对象

#define WSAEVENT                HANDLE

F12跟进去看到,WSAEVENT其实就是一个内核对象句柄


int WSAEventSelect(
  _In_  SOCKET s,               // 绑定到事件对象上的套接字
  _In_  WSAEVENT hEventObject,  // 网络事件对象
  _In_  long lNetworkEvents     // 绑定网络事件监听的消息类型FD_XXX  
);

功能:将指定的网络套接字和需要检测的信号绑定到网络事件上,交由操作系统帮我们监测套接字信号的到来。
返回值:成功返回0,否则返回SOCKET_ERROR


DWORD WSAWaitForMultipleEvents(
  _In_  DWORD cEvents,              //等待的事件总数
  _In_  const WSAEVENT *lphEvents,  //事件数组
  _In_  BOOL fWaitAll,              //是否等待所有事件发生
  _In_  DWORD dwTimeout,            //超时时间(毫秒)
  _In_  BOOL fAlertable             //指定线程是否为alertable等待状态,一般设置为FALSE
);

The WSAWaitForMultipleEvents function returns when one or all of the specified event objects are in the signaled state, when the time-out interval expires, or when an I/O completion routine has executed.
功能:当一个或所有指定的事件对象都处于信号状态、超时、或完成I/O时就返回。
返回值:成功返回产生的事件在事件数组中的索引值,超时返回WSA_WAIT_TIMEOUT,失败返回WSA_WAIT_FAILED,通过WSAGetLastError获取最后的错误码
注意:WSAWaitForMultipleEvents只能将自动重置信号的事件对象设置为无信号状态,所以并不是这里将前面提到的网络事件对象重置为无信号的。WaitForSingleObject、WaitForMultipleObjects都是同理。

fWaitAll为false时,只要有一个事件信号的到来,就进行返回,处理相应事件。如果有并发的事件信号,将会根据事件数组中的顺序遍历,每次返回一个事件处理(类似for的遍历)依次处理完并发的事件。
fWaitAll为true时(通常为false),必须要等到所有的事件对象都发生的时候才会返回,此时如果服务器在处理套接字的时候,有一个套接字失效,但是没有及时消除这个套接字,此时该套接字将永远此时不会有信号到来,就会导致WSAWaitForMultipleEvents总会检测到该套接字无信号,服务器就会在这里一直阻塞。
dwTimeout通常不设置为WSA_INFINITE,否则WSAWaitForMultipleEvents会一直检测多个事件是否有信号到来,此时将大量占用CPU资源,降低服务器的性能。


typedef struct _WSANETWORKEVENTS {
  long lNetworkEvents;              // 网络事件的类型 FD_XXX
  int  iErrorCode[FD_MAX_EVENTS];   // 错误码
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;

This structure is used to store a socket’s internal information about network events.
功能:保存套接字发生的网络事件信息和相关错误码
判断哪个网络事件的方法:通过&运算,判断是lNetworkEvents中的哪一位bit有1,就标志这相应的事件有信号到来…以下是事例

#define FD_READ_BIT      0
#define FD_READ          (1 << FD_READ_BIT)  
lNetworkEvents :0000 0000 0000 0000 0000 0000 0000 0001

#define FD_WRITE_BIT     1
#define FD_WRITE         (1 << FD_WRITE_BIT) 
lNetworkEvents :0000 0000 0000 0000 0000 0000 0000 0010

#define FD_ACCEPT_BIT    3
#define FD_ACCEPT        (1 << FD_ACCEPT_BIT) 
lNetworkEvents :0000 0000 0000 0000 0000 0000 0000 1000

#define FD_CONNECT_BIT   4
#define FD_CONNECT       (1 << FD_CONNECT_BIT) 
lNetworkEvents :0000 0000 0000 0000 0000 0000 0001 0000

#define FD_CLOSE_BIT     5
#define FD_CLOSE         (1 << FD_CLOSE_BIT) 
lNetworkEvents :0000 0000 0000 0000 0000 0000 0010 0000

int WSAEnumNetworkEvents(
  _In_   SOCKET s,              // 套接字
  _In_   WSAEVENT hEventObject, // 事件对象
  _Out_  LPWSANETWORKEVENTS lpNetworkEvents // WSANETWORKEVENTS结构体地址
);

The WSAEnumNetworkEvents function is used to discover which network events have occurred for the indicated socket since the last invocation of this function.
功能:枚举出与事件对象相关联的套接字发生了哪些信号,结果放在WSANETWORKEVENTS结构体中
返回值:成功返回0,否则返回SOCKET_ERROR,错误码通过WSAGetlastError获得

这里我们来解释,为什么前面说的手工重置事件没有WSAResetEvent就变为了无信号的情况。下面是WSAEnumNetworkEvents的MSDN的一段解释:
The Windows Sockets provider guarantees that the operations of copying the network event record, clearing it and resetting any associated event object are automatic, such that the next occurrence of a nominated network event will cause the event object to become set. In the case of this function returning SOCKET_ERROR, the associated event object is not reset and the record of network events is not cleared.
大概意思是说,WSAEnumNetworkEvents复制出套接字中的网络事件记录(存放到WSANETWORKEVENTS结构体中),并且自动的清理掉相应套接字的网络事件信息,并且reset(重置)绑定的事件对象,防止下次发生网络事件的时候该网络事件是set(有信号)的状态。如果失败,则返回SOCK_ERROR。
到这里就解释了为什么没有WSAResetEvent也变成了无信号状态,原来WSAEnumNetworkEvents具有自用重置的功能,底层应该实现了WSAResetEvent。

事件选择模型实现流程图

流程图

_BeginThread()

// listen socket begin accept
void EventSelectModel::_BeginAccept()
{
    SOCKET sockClient = 0;
    while (true)
    {
        sockClient = accept(m_hSocket, NULL, NULL);
        if (sockClient == INVALID_SOCKET)
            continue;
        WSAEVENT newEvent = WSACreateEvent();
        if (SOCKET_ERROR == WSAEventSelect(sockClient, newEvent, FD_READ | FD_CLOSE | FD_WRITE))
        {
            closesocket(sockClient);
            WSACloseEvent(newEvent);
            continue;
        }
        m_eventArray[dwEventTotal] = newEvent;
        m_sockArray[dwEventTotal] = sockClient;
        ++dwEventTotal;
        ++dwClientNums;
    }
}

ServiceProc()

DWORD WINAPI EventSelectModel::ServiceProc(LPARAM lparam)
{
    EventSelectModel* pEventModel = (EventSelectModel*)lparam;
    DWORD dwIndex = 0;
    char buf[1024] = { 0 };
    SOCKET sockClient = INVALID_SOCKET;
    SOCKADDR_IN clientAddr;
    WSANETWORKEVENTS NetWorkEvent = { 0 };
    while (true)
    {
        dwIndex = ::WSAWaitForMultipleEvents(pEventModel->dwEventTotal, pEventModel->m_eventArray, FALSE, INFINITE, FALSE);
        if(dwIndex == WSA_WAIT_FAILED || dwIndex == WSA_WAIT_TIMEOUT)
            continue;
        if (SOCKET_ERROR == ::WSAEnumNetworkEvents(
            pEventModel->m_sockArray[dwIndex - WSA_WAIT_EVENT_0],
            pEventModel->m_eventArray[dwIndex - WSA_WAIT_EVENT_0],
            &NetWorkEvent))
            continue;

        if (NetWorkEvent.lNetworkEvents & FD_READ)//有读的信号
        {
            if (NetWorkEvent.iErrorCode[FD_READ_BIT] != 0)
                continue;
            int nLen = recv(pEventModel->m_sockArray[dwIndex - WSA_WAIT_EVENT_0], buf, sizeof(buf), 0);
            if (nLen == SOCKET_ERROR)
                continue;
            pEventModel->NetFunc(pEventModel->m_sockArray[dwIndex - WSA_WAIT_EVENT_0], buf, strlen(buf));
        }

        if (NetWorkEvent.lNetworkEvents & FD_CLOSE)//有断开的信号
        {
            if (NetWorkEvent.iErrorCode[FD_CLOSE_BIT] != 0 )
                continue;
            closesocket(pEventModel->m_sockArray[dwIndex - WSA_WAIT_EVENT_0]);
            WSACloseEvent(pEventModel->m_eventArray[dwIndex]);
            for (int i = dwIndex; i < pEventModel->dwEventTotal - 1; ++i)
            {
                pEventModel->m_eventArray[i] = pEventModel->m_eventArray[i + 1];
                pEventModel->m_sockArray[i] = pEventModel->m_sockArray[i + 1];
            }
            pEventModel->dwEventTotal--;
            pEventModel->dwClientNums--;
        }

        if (NetWorkEvent.lNetworkEvents & FD_WRITE)//写的信号
        {
            if (NetWorkEvent.iErrorCode[FD_WRITE_BIT] != 0)
                continue;
            //其余操作
        }
    }
    return 0;
}

完整的代码实现:http://pan.baidu.com/s/1nvQgV9n
希望大家指出不足,共同进步

  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值