• 通信编程:WSAEventSelect 模型通信


    信号量机制

    进程同步机制是对多个相关进程在执行次序上进行协调,使并发执行的诸进程之间能按照一定的规则(或时序)共享系统资源。信号量就是用一个变量来表示系统中某种资源的数量,可以利用这种机制来实现同步。
    整型信号量定义为一个用于表示资源数目的整型量 S,S 除了初始化外仅能通过两个标准的原子操作 wait(S)signal(S)来访问,这两个操作也可以被称为 P、V 操作。wait 操作的伪代码如下:

    wait(S){
        while(S <= 0);
        S--;
    }
    

    signal 操作的伪代码如下:

    signal(S){
        S++;
    }
    

    当一个进程在修改某信号量时,由于 P、v 操作是原子操作,因此没有其它进程可同时对该信号量进行修改。

    WSAEventSelect 模型

    WSAEventSelect 模型也是 Winsock 提供的异步事件通知I/O模型,与 WSAAsyncSelect 模型类似,允许应用程序在一个或者多个套接字上接收基于事件的网络通知。不过 WSAEventSelect 模型不是依靠 Windows 的消息驱动机制,而是经由事件对象句柄通知,其机理类似于信号量机制。

    创建事件对象

    WSAEventSelect 模型的基本思路是为需要响应的一组网络事件创建一个事件对象,创建事件对象的函数是 WSACreateEvent(),返回值是一个事件对象句柄。

    WSAEVENT
    WSAAPI
    WSACreateEvent(
        void
        );
    

    接着再调用 WSAEventSelect() 函数将网络事件和事件对象关联起来。当网络事件发生时,Winsock 使相应的事件对象受信,在事件对象上的等待函数就会返回。

    int
    WSAAPI
    WSAEventSelect(
        _In_ SOCKET s,
        _In_opt_ WSAEVENT hEventObject,
        _In_ long lNetworkEvents
        );
    
    参数 说明
    s 套接字句柄
    hEventObject 事件对象句柄
    lNetworkEvents 需要相应的 FD_XXX 网络事件组合

    事件受信

    网络事件与事件对象关联之后,应用程序就可以在事件对象上等待事件了。WSAWaitForMultipleEvents() 函数用于在一个或多个事件对象上等待,当所等待的事件对象受信或者指定的时间过去时,此函数返回。

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

    WSAWaitForMultipleEvents 最多支持 64 个对象,因此这个 I/O 模型在一个线程中同一时间最多能支持 64 个套接字。如果需要使用这个模型管理更多套接字,就需要创建额外的工作线程。WSAWaitForlMultipleEvents 函数会等待网络事件的发生,如果在指定时间内有网络事件发生,函数的返回值会指明是哪一个事件对象促使函数返回的。

    参数 说明
    cEvents 指定 lphEvents 中事件对象句柄的个数
    lphEvents 指向一个事件对象句柄数组
    fWaitAll 指定是否等待所有事件对象都变成受信状态
    dwTimeout 指定要等待的时间,WSA INFINITE 为无穷大
    fAlertable 忽略,设为 FALSE
    • 将 fWaitAll 参数设为 FALSE 以后,如果同时有几个事件对象受信,WSAWaitForMultipleEvents 函数的返回值也仅能指明数组中较前面的事件对象。这样就无法让这些事件对象受信,解决办法是 WSAWaitForMultipleEvents 函数返回后,对每个事件都再次调用 WSAWaitForMultipleEvents 函数,使所有受信的事件对象得到处理。

    查看网络事件

    一旦事件对象受信,就找到与之对应的套接字,然后调用 WSAEnumNetworkEvents() 函数查看发生了什么网络事件。

    int
    WSAAPI
    WSAEnumNetworkEvents(
        _In_ SOCKET s,
        _In_ WSAEVENT hEventObject,
        _Out_ LPWSANETWORKEVENTS lpNetworkEvents
        );
    
    参数 说明
    s 套接字句柄
    hEventObject 事件对象句柄
    lpNetworkEvents 套接字上的网络事件和报错信息

    lpNetworkEvents 是指向一个 WSANETWORKEVENTS 结构的数组,保存了在套接字上发生的网络事件和相关的出错代码。

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

    iErrorCode 参数是一个数组,数组的每个成员对应着一个网络事件的出错代码。

    参数 说明
    lNetworkEvents 指定发生的网络事件
    iErrorCode lNetworkEvents 的出错代码

    WSAEventSelect 模型样例

    注意无论是客户端还是服务器,都需要包含头文件 initsock.h 来载入 Winsock。

    功能设计

    模拟实现 TCP 协议通信过程,要求编程实现服务器端与客户端之间双向数据传递。也就是在一条 TCP 连接中,客户端和服务器相互发送一条数据即可。

    服务器

    使用 WSAEventSelect 模型实现的服务器需要按照如图所示的步骤进行编程,具体编码如下所示。

    #include "initsock.h"
    #include <iostream>
    using namespace std;
    
    // 初始化Winsock库
    CInitSock theSock;
    
    int main()
    {
        // 事件句柄和套节字句柄表
        WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
        SOCKET    sockArray[WSA_MAXIMUM_WAIT_EVENTS];
        int nEventTotal = 0;
    
        // 创建监听套节字
        SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        sockaddr_in sin;
        sin.sin_family = AF_INET;
        sin.sin_port = htons(4567);    // 此服务器监听的端口号
        sin.sin_addr.S_un.S_addr = INADDR_ANY;
        if (::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
        {
            cout << " Failed bind()" << endl;
            return -1;
        }
        // 进入监听模式
        if (::listen(sListen, 5) == SOCKET_ERROR)
        {
            cout << " Failed listen()" << endl;
            return 0;
        }
        cout << "服务器已启动监听,可以接收连接!" << endl;
    
        // 创建事件对象,并关联到新的套节字
        WSAEVENT event = ::WSACreateEvent();
        ::WSAEventSelect(sListen, event, FD_ACCEPT | FD_CLOSE);
        // 添加到表中
        eventArray[nEventTotal] = event;
         sockArray[nEventTotal] = sListen;
        nEventTotal++;
    
        // 处理网络事件
        while (TRUE)
        {
            // 在所有事件对象上等待
            int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);
            // 对每个事件调用WSAWaitForMultipleEvents函数,以便确定它的状态
            nIndex = nIndex - WSA_WAIT_EVENT_0;
            for (int i = nIndex; i < nEventTotal; i++)
            {
                nIndex = ::WSAWaitForMultipleEvents(1, &eventArray[i], TRUE, 1000, FALSE);
                if (nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)
                {
                    continue;
                }
                else
                {
                    // 获取到来的通知消息,WSAEnumNetworkEvents函数会自动重置受信事件
                    WSANETWORKEVENTS event;
                    ::WSAEnumNetworkEvents(sockArray[i], eventArray[i], &event);
                    if (event.lNetworkEvents & FD_ACCEPT)                // 处理FD_ACCEPT通知消息
                    {
                        if (event.iErrorCode[FD_ACCEPT_BIT] == 0)
                        {
                            if (nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
                            {
                                cout << " Too many connections!" << endl;
                                continue;
                            }
                            sockaddr_in addrRemote;
                            int nAddrLen = sizeof(addrRemote);
                            SOCKET sNew = ::accept(sockArray[i], (SOCKADDR*)&addrRemote, &nAddrLen);
                            cout << "
    与主机" << ::inet_ntoa(addrRemote.sin_addr) << "建立连接" << endl;
                            WSAEVENT event = ::WSACreateEvent();
                            ::WSAEventSelect(sNew, event, FD_READ | FD_CLOSE | FD_WRITE);
                            // 添加到表中
                            eventArray[nEventTotal] = event;
                             sockArray[nEventTotal] = sNew;
                            nEventTotal++;
                        }
                    }
                    else if (event.lNetworkEvents & FD_READ)         // 处理FD_READ通知消息
                    {
                        if (event.iErrorCode[FD_READ_BIT] == 0)
                        {
                            char szText[256];
                            int nRecv = ::recv(sockArray[i], szText, strlen(szText), 0);
                            if (nRecv > 0)
                            {
                                szText[nRecv] = '';
                                cout << "  接收到数据:" << szText << endl;
                            }
                            // 向客户端发送数据
                            char sendText[] = "你好,客户端!";
                            if (::send(sockArray[i], sendText, strlen(sendText), 0) > 0)
                            {
                                cout << "  向客户端发送数据:" << sendText << endl;
                            }
                        }
                    }
                    else if (event.lNetworkEvents & FD_CLOSE)        // 处理FD_CLOSE通知消息
                    {
                        if (event.iErrorCode[FD_CLOSE_BIT] == 0)
                        {
                            ::closesocket(sockArray[i]);
                            for (int j = i; j < nEventTotal - 1; j++)
                            {
                                eventArray[j] = eventArray[j + 1];
                                 sockArray[j] =  sockArray[j + 1];
                            }
                            nEventTotal--;
                        }
                    }
                }
            }
        }
        return 0;
    }
    

    客户端

    #include "InitSock.h"
    #include <iostream>
    using namespace std;
     
    CInitSock initSock;     // 初始化Winsock库
     
    int main()
    {
        // 创建套节字
        SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (s == INVALID_SOCKET)
        {
            cout << " Failed socket()" << endl;
            return 0;
        }
     
        // 也可以在这里调用bind函数绑定一个本地地址
        // 否则系统将会自动安排
        char address[20] = "127.0.0.1";
        // 填写远程地址信息
        sockaddr_in servAddr;
        servAddr.sin_family = AF_INET;
        servAddr.sin_port = htons(4567);
        // 注意,这里要填写服务器程序(TCPServer程序)所在机器的IP地址
        // 如果你的计算机没有联网,直接使用127.0.0.1即可
        servAddr.sin_addr.S_un.S_addr = inet_addr(address);
     
        if (::connect(s, (sockaddr*)&servAddr, sizeof(servAddr)) == -1)
        {
            cout << " Failed connect() " << endl;
            return 0;
        }
        else 
        {
            cout << "与服务器 " << address << "建立连接" << endl;
        }
     
        char szText[] = "你好,服务器!";
        if (::send(s, szText, strlen(szText), 0) > 0)
        {
            cout << "  发送数据:" << szText << endl;
        }
     
        // 接收数据
        char buff[256];
        int nRecv = ::recv(s, buff, 256, 0);
        if (nRecv > 0)
        {
            buff[nRecv] = '';
            cout << "  接收到数据:" << buff << endl;
        }
        
        // 关闭套节字
        ::closesocket(s);
        return 0;
    }
    

    运行效果

    参考资料

    《Windows 网络与通信编程》,陈香凝 王烨阳 陈婷婷 张铮 编著,人民邮电出版社

  • 相关阅读:
    CSS
    人物
    CSS
    CSS
    概念- 工业4.0
    C#正则表达式
    六月定律
    c#中实现登陆窗口(无需隐藏)
    c#中关于String、string,Object、object,Int32、int
    一个快速找第k+1小的算法
  • 原文地址:https://www.cnblogs.com/linfangnan/p/15450988.html
Copyright © 2020-2023  润新知