WSAEventSelect网络模型

一、WSAEventSelect网络事件模型介绍:

  事件选择(WSAEventSelect)模型是另一个有用的I/O模型,和WSAAsyncSelect模型类似的是,他也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知,最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递到一个窗口句柄。

二、使用具体步骤:

1.事件通知模型要求我们的应用程序针对使用的每一个套接字,首先创建一个事件对象。创建方法是调用WSACreateEvent函数,定义如下:

WSAEVENT WSACreateEvent(void)

WSACreateEvent函数的返回值很简单,就是一个创建好的事件对象句柄。
WSACreateEvent创建的事件有两种工作状态,以及两种工作模式。

工作状态分别是:已传信和未传信。
工作模式分别是:人工重设和自动重设

WSACreateEvent开始是以一种未传信的工作状态,并用一种人工重设模式来创建事件对象句柄。随着网络事件触发了与一个套接字管理子啊一起的事件对象,工作状态便会从未传信转变为
已传信,由于事件对象是在一种人工重设模式中创建的,所以在完成了一个I/O请求的处理之后,我们的应用程序需要负责将工作状态从已传信更改为未传信,就得调用WSAResetEvent函数
定义如下:

BOOL WSAAPI WSAResetEvent(
    _In_ WSAEVENT hEvent
    );

hEvent:一个事件对象句柄,基于调用是成功还是失败,会分别返回TRUE或FALSE。

2.接下来必须将其与某个套接字关联在一起,同事注册自己感兴趣的网络事件类型(FD_READ、FD_WRITE、FD_ACCETP、FD_CONNECT、FD_CLOSE),方法是调用WSAEventSelect函数,其定义如下:

int WSAAPI WSAEventSelect(
        _In_ SOCKET s,
        _In_opt_ WSAEVENT hEventObject,
        _In_ long lNetworkEvents
        );

s:代表感兴趣的套接字
hEventObject:指定要与套接字管理在一起的事件对象---用WSACreateEvent取得的那一个。
lNetworkEvents:则对应一个“位掩码”,用于指定应用程序感兴趣的各种网络事件类型的一个组合。

3.对一个事件对象处理完或者某个socket关闭之后,应关闭对应的事件对象,调用WSACloseEvent函数

BOOL WSAAPI WSACloseEvent(
    _In_ WSAEVENT hEvent
    );

4.一个套接字同一个事件对象句柄管理在一起后,应用程序便可开始I/O处理:方法是等待网络事件触发对象句柄的工作状态,WSAWaitForMultipleEvents函数的设计
宗旨便是用来等待一个或多个事件对象句柄,并在事先指定的一个或所有句柄进入已传信状态后,或者超过了一个规定的事件周期后,立即返回。函数定义如下:

DWORD WSAAPI WSAWaitForMultipleEvents(
    _In_ DWORD cEvents,
    _In_reads_(cEvents) const WSAEVENT FAR * lphEvents,
    _In_ BOOL fWaitAll,
    _In_ DWORD dwTimeout,
    _In_ BOOL fAlertable
    );

cEvents和lphEvents:由WSAEVENT对象构成的一个数组,cEvents指定事件对象的数量,而lphEvents对应一个指针,用于直接引用该数组

注意:WSAWaitForMultipleEvents只能支持由WSA_MAXIMUM_WAIT_EVENTS对象规定的一个最大值,在此定义成64个,因此,针对发出WSAWaitForMultipleEvents调用的每个线程,该I/O模型
每一次最多支持64个套接字,假如想让这个模型同事管理不止64个套接字,必须创建额外的工作线程,以便等待更多的事件对象。
fWaitAll:若为TRUE,必须等到lphEvents数组包含所以事件对象都进入已传信状态,函数才能返回,若为FALSE,则任何一个事件对象进入已传信状态,函数返回。
dwTimeout:超时设定,单位毫秒,设定为WSAINFINITE时,永远等待。
fAlertable:一般设置为FALSE.
若WSAWaitForMultipleEvents收到一个事件对象通知,便会返回一个值,指出造成函数返回的事件对象。此时应该用WSAWaitForMultipleEvents的返回值,减去预定义的值WSA_WAIT_EVENT_0,
得到具体的引用值(即索引位置),如下:

Index = WSAWaitForMultipleEvents(...);
MyEvent = EventArray[Index - WSA_WAIT_EVENT_0];

5.知道了造成网络事件的套接字后,接下来调用WSAEnumNetworkEvents,定义如下:

int WSAAPI WSAEnumNetworkEvents(
    _In_ SOCKET s,
    _In_ WSAEVENT hEventObject,
    _Out_ LPWSANETWORKEVENTS lpNetworkEvents
    );

s:造成网络事件的套接字
hEventObject:是可选的,指定了一个事件句柄,对应于打算重设的那个事件对象,由于我们的事件对象处在一个已传信状态,所以可将它传入,令其自动成为未传信状态,如果不想用
hEventObject参数来重设事件,那么可使用WSAResetEvent函数,
lpNetworkEvents:代表一个指针,指向WSANETWORKEVENTS结构,存储套接字上发生的网络事件类型以及可以出现的任何错误代码。
WSANETWORKEVENTS结构如下:

typedef struct _WSANETWORKEVENTS {
       long lNetworkEvents;
       int iErrorCode[FD_MAX_EVENTS];
    } WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;

lNetworkEvents:对应套接字上发生的所以网络事件类型(FD_READ、FD_WRITE等)。
iErrorCode:指定一个错误代码数组,同lNetworkEvents中的事件关联在一起。

6.针对每个网络事件类型,都存在一个特殊的事件索引,名字与事件类型的名字类似,只要在事件名字后面加一个“_BIT”后缀字符串即可,例如对FD_READ事件类型来说,iErrorCode
数组的索引标识便是FD_READ_BIT,

if (lpNetworkEvents.lNetworkEvents & FD_READ)
{
    if (lpNetworkEvents.iErrorCode[FD_READ_BIT != 0])
    {
        printf("FD_READ falied with error %d
",lpNetworkEvents.iErrorCode[FD_READ_BIT]);
    }
}

三、具体实现代码:

void CMFCApplication1Dlg::OnBnClickedButton1()
{
    // 服务器启动网络I/O线程
    AfxBeginThread(ThreadProc, this, THREAD_PRIORITY_NORMAL, 4 * 1024 * 1024, 0);
}

UINT CMFCApplication1Dlg::ThreadProc(LPVOID pParam)
{
    ASSERT(pParam);
    sockaddr_in clientAddr = { 0 };
    /////////////////////////////////
    TCHAR szBuf[MAX_BUF_SIZE] = { 0 };
    SOCKET ArrSocket[WSA_MAXIMUM_WAIT_EVENTS] = { 0 };
    WSAEVENT ArrEvent[WSA_MAXIMUM_WAIT_EVENTS] = { 0 };
    DWORD dwTotal = 0, dwIndex = 0, index = 0, ret = 0;
    WSANETWORKEVENTS m_NetWorkEvents = { 0 };
    /////////////////////////////////
    CMFCApplication1Dlg *pThis = (CMFCApplication1Dlg *)pParam;
    pThis->WinSockInit();
    pThis->m_SockListen = INVALID_SOCKET;
    pThis->m_SockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (pThis->m_SockListen == INVALID_SOCKET)
        return FALSE;
    sockaddr_in service;
    service.sin_family = AF_INET;
    service.sin_addr.s_addr = INADDR_ANY;
    service.sin_port = htons(9527);

    if (bind(pThis->m_SockListen, (sockaddr *)&service, sizeof(sockaddr_in)) == SOCKET_ERROR)
        return FALSE;

    WSAEVENT m_ListenEvent = WSACreateEvent();
    //事件对象与socket绑定
    WSAEventSelect(pThis->m_SockListen, m_ListenEvent, FD_ACCEPT | FD_CLOSE);

    if (listen(pThis->m_SockListen, SOMAXCONN) == SOCKET_ERROR)
        return FALSE;

    ArrSocket[dwIndex] = pThis->m_SockListen;
    ArrEvent[dwIndex] = m_ListenEvent;
    dwTotal++;
    
    while (TRUE)
    {
        //等待网络事件
        dwIndex = WSAWaitForMultipleEvents(dwTotal, ArrEvent, FALSE, INFINITE, FALSE);
        if (dwIndex == WSA_WAIT_FAILED || dwIndex == WSA_WAIT_TIMEOUT)
            continue;
        index = dwIndex - WSA_WAIT_EVENT_0;
        WSAEnumNetworkEvents(ArrSocket[index], ArrEvent[index], &m_NetWorkEvents);

        //底下为对应网络事件的处理

        //accept the client
        if (m_NetWorkEvents.lNetworkEvents & FD_ACCEPT)
        {
            if (m_NetWorkEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
                continue;
            pThis->m_SockClient = accept(ArrSocket[index], NULL, NULL);
            if (pThis->m_SockClient == INVALID_SOCKET)
                continue;
            WSAEVENT newEvent = WSACreateEvent();
            WSAEventSelect(pThis->m_SockClient, newEvent, FD_READ | FD_WRITE | FD_CLOSE);

            ArrSocket[dwTotal] = pThis->m_SockClient;
            ArrEvent[dwTotal] = newEvent;
            dwTotal++;
        }
        //recv the data
        if (m_NetWorkEvents.lNetworkEvents & FD_READ)
        {
            if (m_NetWorkEvents.iErrorCode[FD_READ_BIT] != 0)
                continue;
            ret = recv(ArrSocket[index], (char *)szBuf, MAX_BUF_SIZE, 0);
            //更新界面接收数据
            if (ret > 0)
                pThis->ShowMsg(szBuf);
            else
            {
                pThis->ShowMsg(TEXT("客户端已下线,请重新开启服务器等待客户端连接"));
                continue;
            }
        }
        //send the data
        if (m_NetWorkEvents.lNetworkEvents & FD_WRITE)
        {
            if (m_NetWorkEvents.iErrorCode[FD_WRITE_BIT] != 0)
                continue;
            //发送数据
        }
        //close the socket
        if (m_NetWorkEvents.lNetworkEvents & FD_CLOSE)
        {
            if (m_NetWorkEvents.iErrorCode[FD_CLOSE_BIT] != 0)
                continue;
            //关闭对应的socket
            closesocket(ArrSocket[index]);
            //关闭socket对应的事件对象
            WSACloseEvent(ArrEvent[index]);
            //去掉2个数组中对应的东西,并且总数-1
            dwTotal--;
            pThis->DelValue(ArrSocket, WSA_MAXIMUM_WAIT_EVENTS, index);
            pThis->DelValue(ArrEvent, WSA_MAXIMUM_WAIT_EVENTS, index);
        }
    }

    if (pThis->m_SockListen != INVALID_SOCKET)
        closesocket(pThis->m_SockListen);

    WSACleanup();

    return TRUE;
}

//根据索引删除数组中的值
template <typename T>
BOOL CMFCApplication1Dlg::DelValue(T arr[], int arr_size, int index)
{
    if (arr || index >= 0)
    {
        for (int i = index; i<arr_size - 1; ++i)
            arr[i] = arr[i + 1];
    }
    return TRUE;
}


BOOL CMFCApplication1Dlg::WinSockInit()
{
    WSADATA data = { 0 };
    if (WSAStartup(MAKEWORD(2, 2), &data))
        return FALSE;
    if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2)
    {
        WSACleanup();
        return FALSE;
    }

    return TRUE;
}
View Code
111
原文地址:https://www.cnblogs.com/zwj-199306231519/p/13839110.html