1. 程式人生 > WINDOWS開發 >WinSock WSAEventSelect模型

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,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);
        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,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",(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