WinSock WSAEventSelect模型

概念:WSAEventSelect模型是Windows Sockets提供的一个有用异步I/O模型。该模型允许在一个或者多个套接字上接收以事件为基础的网络事件通知。Windows Sockets应用程序在创建套接字后,调用WSAEventSelect()函数,将一个事件对象与网络事件集合关联在一起。当网络事件发生时,应用程序以事件的形式接收网络事件通知。

基本流程 :

  1. 初始化网络环境,创建一个监听的socket,然后进行connect操作。接下来WSACreateEvent()创建一个网络事件对象,其声明如下:
    WSAEVENT WSACreateEvent(void); //返回一个手工重置的事件对象句柄
  2. 再调用WSAEventSelect,来将监听的socket与该事件进行一个关联,其声明如下:
    int WSAEventSelect(    
      SOCKET s,                 //套接字  
      WSAEVENT hEventObject,    //网络事件对象  
      long lNetworkEvents       //需要关注的事件  
    ); 

    我们客户端只关心FD_READ和FD_CLOSE操作,所以第三个参数传FD_READ | FD_CLOSE。

  3. 启动一个线程调用WSAWaitForMultipleEvents等待1中的event事件,其声明如下:
    复制代码
    DWORD WSAWaitForMultipleEvents(    
      DWORD cEvents,                  //指定了事件对象数组里边的个数,最大值为64  
      const WSAEVENT FAR *lphEvents,  //事件对象数组  
      BOOL fWaitAll,                  //等待类型,TRUE表示要数组里全部有信号才返回,FALSE表示至少有一个就返回,这里必须为FALSE  
      DWORD dwTimeout,                //等待的超时时间  
      BOOL fAlertable                 //当系统的执行队列有I/O例程要执行时,是否返回,TRUE执行例程返回,FALSE不返回不执行,这里为FALSE  
    );  
    复制代码

    由于我们是客户端,所以只等待一个事件。

  4. 当事件发生,我们需要调用WSAEnumNetworkEvents,来检测指定的socket上的网络事件。其声明如下:
    复制代码
    int WSAEnumNetworkEvents  
    (    
      SOCKET s,                             //指定的socket  
      WSAEVENT hEventObject,                //事件对象  
      LPWSANETWORKEVENTS lpNetworkEvents    //WSANETWORKEVENTS<span >结构地址</span>  
    );  
    复制代码

    当我们调用这个函数成功后,它会将我们指定的socket和事件对象所关联的网络事件的信息保存到WSANETWORKEVENTS这个结构体里边去,我们来看下这个结构体的声明:

    typedef struct _WSANETWORKEVENTS {  
      long     lNetworkEvents;<span style="white-space:pre">          </span>//指定了哪个已经发生的网络事件  
      int      iErrorCodes[FD_MAX_EVENTS];<span style="white-space:pre">      </span>//错误码  
    } WSANETWORKEVENTS, *LPWSANETWORKEVENTS;  

    根据这个结构体我们就可以判断是否是我们所关注的网络事件已经发生了。如果是我们的读的网络事件发生了,那么我们就调用recv函数进行操作。若是关闭的事件发生了,就调用closesocket将socket关掉,在数组里将其置零等操作。

  5. 整个模型的流程图如下:整个模型的流程图如下:
  6. 代码

    #pragma once
    #include "stdafx.h"
    #include <WinSock2.h>
    #include <Windows.h>
    
    // 释放指针的宏
    #define RELEASE(x)            {if(x != NULL) {delete x; x = NULL;}}
    // 释放句柄的宏
    #define RELEASE_HANDLE(x)    {if(x != NULL && x != INVALID_HANDLE_VALUE) { CloseHandle(x); x = INVALID_HANDLE_VALUE; }}
    // 释放Socket的宏
    #define RELEASE_SOCKET(x)    {if(x != INVALID_SOCKET) { closesocket(x); x = INVALID_SOCKET; }}
    
    class ClientBase
    {
    public:
        ClientBase();
        ~ClientBase();
    
        // 启动通信
        BOOL Start(const char *IPAddress, USHORT port);
        // 关闭通信
        BOOL Stop();
        // 发送数据
        BOOL Send(const BYTE* buffer, int len);
        // 是否已启动
        BOOL HasStarted();
    
        // 事件通知函数(派生类重载此族函数)
        // 连接关闭
        virtual void OnConnectionClosed() = 0;
        // 连接上发生错误
        virtual void OnConnectionError() = 0;
        // 读操作完成
        virtual void OnRecvCompleted(BYTE* buffer, int len) = 0;
        // 写操作完成
        virtual void OnSendCompleted() = 0;
    
    private:
        // 接收线程函数
        static DWORD WINAPI RecvThreadProc(LPVOID lpParam);
        // socket是否存活
        BOOL IsSocketAlive(SOCKET sock);
        SOCKET clientSock;
        WSAEVENT socketEvent;
        HANDLE stopEvent;
        HANDLE thread;
    };
    #include "ClientBase.h"
    #include <WS2tcpip.h>
    
    #pragma comment(lib, "WS2_32.lib")
    
    ClientBase::ClientBase()
    {
        WSADATA wsaData;
        WSAStartup(MAKEWORD(2, 2), &wsaData);
    }
    
    
    ClientBase::~ClientBase()
    {
        WSACleanup();
    }
    
    BOOL ClientBase::Start(const char *IPAddress, USHORT port)
    {
        clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (clientSock == INVALID_SOCKET)
            return false;
        socketEvent = WSACreateEvent();
        stopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    
        sockaddr_in serAddr;
        serAddr.sin_family = AF_INET;
        serAddr.sin_port = htons(port);
        inet_pton(AF_INET, IPAddress, &serAddr.sin_addr);
        //serAddr.sin_addr.S_un.S_addr = inet_addr(IPAddress);
        if (connect(clientSock, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
        {  //连接失败
            closesocket(clientSock);
            return false;
        }
        if (0 != WSAEventSelect(clientSock, socketEvent, FD_READ | FD_CLOSE))
            return false;
    
        thread = CreateThread(0, 0, RecvThreadProc, (void *)this, 0, 0);
        return true;
    }
    
    BOOL ClientBase::Stop()
    {
        SetEvent(stopEvent);
        WaitForSingleObject(thread, INFINITE);
        RELEASE_SOCKET(clientSock);
        WSACloseEvent(socketEvent);
        RELEASE_HANDLE(stopEvent);
        return true;
    }
    
    BOOL ClientBase::Send(const BYTE * buffer, int len)
    {
        if (SOCKET_ERROR == send(clientSock, (char*)buffer, len, 0))
        {
            return false;
        }
        return true;
    }
    
    BOOL ClientBase::HasStarted()
    {
        return 0;
    }
    
    DWORD ClientBase::RecvThreadProc(LPVOID lpParam)
    {
        if (lpParam == NULL)
            return 0;
    
        ClientBase *client = (ClientBase *)lpParam;
        DWORD ret = 0;
        int index = 0;
        WSANETWORKEVENTS networkEvent;
        HANDLE events[2];
        events[0] = client->socketEvent;
        events[1] = client->stopEvent;
    
        while (true)
        {
            ret = WSAWaitForMultipleEvents(2, events, FALSE, INFINITE, FALSE);
            if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
                continue;
            index = ret - WSA_WAIT_EVENT_0;
            if (index == 0)
            {
                WSAEnumNetworkEvents(client->clientSock, events[0], &networkEvent);
                if (networkEvent.lNetworkEvents & FD_READ)
                {
                    if (networkEvent.iErrorCode[FD_READ_BIT != 0])
                    {
                        //Error
                        continue;
                    }
                    char *buff = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 4096);
                    ret = recv(client->clientSock, buff, 4096, 0);
                    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
                    {
                        client->OnConnectionClosed();
                        break;        //错误
                    }
                    client->OnRecvCompleted((BYTE*)buff, ret);
                }
                if (networkEvent.lNetworkEvents & FD_CLOSE)
                {
    
                    client->OnConnectionClosed();
                    break;    //关闭
                }
            }
            else
            {
                client->OnConnectionClosed();
                break;    // 退出
            }
    
        }
        return 1;
    }
    
    BOOL ClientBase::IsSocketAlive(SOCKET sock)
    {
        return 0;
    }
    #include "ClientBase.h"
    #include <stdio.h>
    
    class Client : public ClientBase
    {
    public:
        // 连接关闭
        virtual void OnConnectionClosed()
        {
            printf("   Close\n");
        }
        // 连接上发生错误
        virtual void OnConnectionError()
        {
            printf("   Error\n");
        }
        // 读操作完成
        virtual void OnRecvCompleted(BYTE* buffer, int len)
        {
            printf("recv[%d]:%s\n", len, (char*)buffer);
        }
        // 写操作完成
        virtual void OnSendCompleted()
        {
            printf("*Send success\n");
        }
    
    };
    
    int main()
    {
        Client client;
        if (!client.Start("127.0.0.1", 10240))
        {
            printf("   start error\n");
        }
    
        int i = 0;
        while (true)
        {
            char buff[128];
            //scanf_s("%s", &buff, 128);
    
    
            sprintf_s(buff, 128, "第%d条Msg", i++);
            Sleep(500);
            client.Send((BYTE*)buff, strlen(buff)+1);
        }
    }

    转载:https://www.cnblogs.com/tanguoying/p/8506821.html

    扫描二维码关注公众号,回复: 11336290 查看本文章

猜你喜欢

转载自www.cnblogs.com/zwj-199306231519/p/13174526.html