zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    单元测试笔记
    centos7安装rabbitmq
    spring cache之redis使用示例
    ObjectMapper序列化时间
    安装alertmanager
    prometheus安装
    Ribbon配置随访问策略
    优化if..else代码的两种方式
    spring bean的生命周期
    idea热部署
  • 原文地址:https://www.cnblogs.com/zwj-199306231519/p/13839110.html
Copyright © 2011-2022 走看看