1. 程式人生 > >WinSock WSAEventSelect 模型總結

WinSock WSAEventSelect 模型總結

efi pri 函數 auto error str 介紹 windows.h acl

前言

 本文配套代碼:https://github.com/TTGuoying/WSAEventSelect-model

  由於篇幅原因,本文假設你已經熟悉了利用Socket進行TCP/IP編程的基本原理,並且也熟練的掌握了多線程編程技術,太基本的概念我這裏就略過不提了,網上的資料應該遍地都是。

  上一篇文章介紹的IOCP模型主要用於服務器,客戶端的話一般WSAEventSelect模型,下面介紹 WSAEventSelect 模型。

  由於網絡編程中數據何時到來的不可預知,如果我們在線程中用recv()函數一直等待數據的到來會造成cpu的極大浪費,事件選擇(WSAEventSelect)模型可以避免這個問題。事件選擇(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 style="font-family:Arial, Helvetica, sans-serif;">結構地址</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關掉,在數組裏將其置零等操作。

  整個模型的流程圖如下:

技術分享圖片

實現(配合IOCP服務器類測試更佳)

 1 #pragma once
 2 #include "stdafx.h"
 3 #include <WinSock2.h>
 4 #include <Windows.h>
 5 
 6 // 釋放指針的宏
 7 #define RELEASE(x)            {if(x != NULL) {delete x; x = NULL;}}
 8 // 釋放句柄的宏
 9 #define RELEASE_HANDLE(x)    {if(x != NULL && x != INVALID_HANDLE_VALUE) { CloseHandle(x); x = INVALID_HANDLE_VALUE; }}
10 // 釋放Socket的宏
11 #define RELEASE_SOCKET(x)    {if(x != INVALID_SOCKET) { closesocket(x); x = INVALID_SOCKET; }}
12 
13 class ClientBase
14 {
15 public:
16     ClientBase();
17     ~ClientBase();
18 
19     // 啟動通信
20     BOOL Start(const char *IPAddress, USHORT port);    
21     // 關閉通信
22     BOOL Stop();    
23     // 發送數據
24     BOOL Send(const BYTE* buffer, int len);    
25     // 是否已啟動
26     BOOL HasStarted();    
27 
28     // 事件通知函數(派生類重載此族函數)
29     // 連接關閉
30     virtual void OnConnectionClosed() = 0;
31     // 連接上發生錯誤
32     virtual void OnConnectionError() = 0;
33     // 讀操作完成
34     virtual void OnRecvCompleted(BYTE* buffer, int len) = 0;
35     // 寫操作完成
36     virtual void OnSendCompleted() = 0;
37 
38 private:
39     // 接收線程函數
40     static DWORD WINAPI RecvThreadProc(LPVOID lpParam); 
41     // socket是否存活
42     BOOL IsSocketAlive(SOCKET sock);
43     SOCKET clientSock;
44     WSAEVENT socketEvent;
45     HANDLE stopEvent;
46     HANDLE thread;
47 };

  1 #include "ClientBase.h"
  2 #include <WS2tcpip.h>
  3 
  4 #pragma comment(lib, "WS2_32.lib")
  5 
  6 ClientBase::ClientBase()
  7 {
  8     WSADATA wsaData;
  9     WSAStartup(MAKEWORD(2, 2), &wsaData);
 10 }
 11 
 12 
 13 ClientBase::~ClientBase()
 14 {
 15     WSACleanup();
 16 }
 17 
 18 BOOL ClientBase::Start(const char *IPAddress, USHORT port)
 19 {
 20     clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 21     if (clientSock == INVALID_SOCKET)
 22         return false;
 23     socketEvent = WSACreateEvent();
 24     stopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
 25 
 26     sockaddr_in serAddr;
 27     serAddr.sin_family = AF_INET;
 28     serAddr.sin_port = htons(port);
 29     inet_pton(AF_INET, IPAddress, &serAddr.sin_addr);
 30     //serAddr.sin_addr.S_un.S_addr = inet_addr(IPAddress);
 31     if (connect(clientSock, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
 32     {  //連接失敗   
 33         closesocket(clientSock);
 34         return false;
 35     }
 36     if (0 != WSAEventSelect(clientSock, socketEvent, FD_READ | FD_CLOSE))
 37         return false;
 38 
 39     thread = CreateThread(0, 0, RecvThreadProc, (void *)this, 0, 0);
 40     return true;
 41 }
 42 
 43 BOOL ClientBase::Stop()
 44 {
 45     SetEvent(stopEvent);
 46     WaitForSingleObject(thread, INFINITE);
 47     RELEASE_SOCKET(clientSock);
 48     WSACloseEvent(socketEvent);
 49     RELEASE_HANDLE(stopEvent);
 50     return true;
 51 }
 52 
 53 BOOL ClientBase::Send(const BYTE * buffer, int len)
 54 {
 55     if (SOCKET_ERROR == send(clientSock, (char*)buffer, len, 0))
 56     {
 57         return false;
 58     }
 59     return true;
 60 }
 61 
 62 BOOL ClientBase::HasStarted()
 63 {
 64     return 0;
 65 }
 66 
 67 DWORD ClientBase::RecvThreadProc(LPVOID lpParam)
 68 {
 69     if (lpParam == NULL)
 70         return 0;
 71 
 72     ClientBase *client = (ClientBase *)lpParam;
 73     DWORD ret = 0;
 74     int index = 0;
 75     WSANETWORKEVENTS networkEvent;
 76     HANDLE events[2];
 77     events[0] = client->socketEvent;
 78     events[1] = client->stopEvent;
 79 
 80     while (true)
 81     {
 82         ret = WSAWaitForMultipleEvents(2, events, FALSE, INFINITE, FALSE);
 83         if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
 84             continue;
 85         index = ret - WSA_WAIT_EVENT_0;
 86         if (index == 0)
 87         {
 88             WSAEnumNetworkEvents(client->clientSock, events[0], &networkEvent);
 89             if (networkEvent.lNetworkEvents & FD_READ)
 90             {
 91                 if (networkEvent.iErrorCode[FD_READ_BIT != 0])
 92                 {
 93                     //Error
 94                     continue;
 95                 }
 96                 char *buff = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 4096);
 97                 ret = recv(client->clientSock, buff, 4096, 0);
 98                 if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
 99                 {
100                     client->OnConnectionClosed();
101                     break;        //錯誤
102                 }
103                 client->OnRecvCompleted((BYTE*)buff, ret);
104             }
105             if (networkEvent.lNetworkEvents & FD_CLOSE)
106             {
107 
108                 client->OnConnectionClosed();
109                 break;    //關閉
110             }
111         }
112         else
113         {
114             client->OnConnectionClosed();
115             break;    // 退出
116         }
117         
118     }
119     return 1;
120 }
121 
122 BOOL ClientBase::IsSocketAlive(SOCKET sock)
123 {
124     return 0;
125 }
 1 #include "ClientBase.h"
 2 #include <stdio.h>
 3 
 4 class Client : public ClientBase
 5 {
 6 public:
 7     // 連接關閉
 8     virtual void OnConnectionClosed()
 9     {
10         printf("   Close\n");
11     }
12     // 連接上發生錯誤
13     virtual void OnConnectionError()
14     {
15         printf("   Error\n");
16     }
17     // 讀操作完成
18     virtual void OnRecvCompleted(BYTE* buffer, int len)
19     {
20         printf("recv[%d]:%s\n", len, (char*)buffer);
21     }
22     // 寫操作完成
23     virtual void OnSendCompleted()
24     {
25         printf("*Send success\n");
26     }
27 
28 };
29 
30 int main()
31 {
32     Client client;
33     if (!client.Start("127.0.0.1", 10240))
34     {
35         printf("   start error\n");
36     }
37 
38     int i = 0;
39     while (true)
40     {
41         char buff[128];
42         //scanf_s("%s", &buff, 128);
43         
44 
45         sprintf_s(buff, 128, "第%d條Msg", i++);
46         Sleep(500);
47         client.Send((BYTE*)buff, strlen(buff)+1);
48     }
49 }

WinSock WSAEventSelect 模型總結