1. 程式人生 > >windows上的5種網路通訊模型示例程式碼

windows上的5種網路通訊模型示例程式碼

一些好設計的經驗:

linux網路:

高效能網路程式設計IO複用和Epoll高效率之處-遍歷的集合更小空間換時間/水平觸發和邊緣觸發主動返回。

反應堆的設計模式-避免C風格的一個應用邏輯都需要處理多個物件而是用OO設計模式方式分離。

windows網路:

select模型,WSAAsyncSelect模型,WSAEventSelect模型,重疊Overlapped IO模型,完全埠IO Completion Port模型。

是遵循定期檢查,視窗事件通知,事件物件通知,多執行緒重疊IO和事件物件完成通知,事件物件完成通知和通過完成埠訊息佇列有效的管理外部和內部的執行緒池,進化來提高網路通訊模型。

0.客戶端設計
#include "stdafx.h"
#include <WINSOCK2.H>  
#include <stdio.h>  
#define SERVER_ADDRESS "127.0.0.1"  
#define PORT           5150
#define MSGSIZE        8192 // window作業系統預設socket收發快取大小是8K
#pragma comment(lib, "ws2_32.lib")  

void CheckBuffer(SOCKET &socket)
{
 //window 7,sock2,預設核心傳送快取和接收快取都是8K.
 int sendbuflen = 0;  
 int len = sizeof(sendbuflen);  
 getsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char*)&sendbuflen, &len);  
 printf("default,sendbuf:%d\n", sendbuflen);
 
 getsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&sendbuflen, &len);  
 printf("default,recvbuf:%d\n", sendbuflen);
 /*sendbuflen = 10240;  
 setsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, len);  */
}

bool LoadWSAData(WSADATA &wsaData)
{
 // Initialize Windows socket library  
 WORD wVersionRequested = MAKEWORD(2, 2);
 // MAKEWORD的作用,類似下面
 WORD wHigh = 2;
 WORD wLow = 2;
 WORD wAll = ((wHigh << 8) | wLow);
 // 初始化只需要傳入版本號,和WSADATA就可以了
 int reqErr = ::WSAStartup(wVersionRequested, &wsaData);
 if(reqErr != 0)
 {
  printf("載入請求指定版本的windows socket api DLL 失敗");
  return false;
 }
 /* Confirm that the WinSock DLL supports 2.2.*/
 /* Note that if the DLL supports versions greater    */
 /* than 2.2 in addition to 2.2, it will still return */
 /* 2.2 in wVersion since that is the version we      */
 /* requested.                                        */
 if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
  /* Tell the user that we could not find a usable */
  /* WinSock DLL.                                  */
  printf("Could not find a usable version of Winsock.dll\n");
  ::WSACleanup();
  return false;
 }
 else
 {
  printf("The Winsock 2.2 dll was found okay\n");
  return true;
 }
}

void ReleaseWSAData()
{
 ::WSACleanup();
}

int _tmain(int argc, _TCHAR* argv[])
{

 WSADATA     wsaData;  
 SOCKET      sClient;  
 SOCKADDR_IN server;  
 char        szMessage[MSGSIZE];// 所有整數浮點數,非數值編碼型別都可以轉換為char/unsigned char的十六進位制

 if(!LoadWSAData(wsaData))
 {
  return 0;
 }
 // Create client socket  
 // 返回一個socket描述符,類似檔案描述符,指標
 sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 協議族,socket型別,具體協議
 if(INVALID_SOCKET == sClient)
 {
  printf("Get Socket Error: INVALID_SOCKET.\n");
  return 0;
 }
 CheckBuffer(sClient);
 
 // Connect to server  
 memset(&server, 0, sizeof(SOCKADDR_IN));
 // 網路socket三元組,網路型別,地址,埠
 server.sin_family = AF_INET;  
 server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);  
 server.sin_port = htons(PORT);  
 // 協議地址埠,來標識一個server程序,都要轉換為網路位元組順序
 int nLen = sizeof(SOCKADDR_IN);// 大小為16 Byte,剛好是記憶體對齊模式,sockaddr也是16 Byte
 // 阻塞模式connect會阻塞程式,客戶端的connect在三次握手的第二個次返回,而伺服器端的accept在三次握手的第三次返回。
 // 非阻塞會馬上返回
 // connect時候會隨機分配一個埠和地址給當前客戶端網路程序,伺服器會收到
 int nConnect = connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));
 if(SOCKET_ERROR == nConnect)
 {
  printf("Socket connnect Error.\n");
  return 0;
 }

 while (TRUE)  
 {  
  printf("Send Msg:");  
  gets(szMessage);  
  // Send message  
  // 阻塞模式下:傳送前會先檢查傳送快取是否在傳送資料,是等待,不在傳送則檢查核心傳送快取,比較大小,
  // 如果小於那麼拷貝到傳送快取,否則等待。
  // 非阻塞模式下:先檢查傳送快取是否在傳送,是等待,不是馬上拷貝傳送,能拷貝多少就拷貝多少。
  // 拷貝到核心傳送快取出錯,那麼返回SOCKET_ERROR,等待或者拷貝的過程中網路斷開也返回SOCKET_ERROR
  int nSendRes = send(sClient, szMessage, strlen(szMessage), 0);// strlen求得的字串長度不包含'\0'
  if(SOCKET_ERROR == nSendRes)
  {
   printf("Send Copy data kernel buffer is too small or network shutdown!\n");
  }

  // Receive message
  // 接收訊息前會先檢查傳送快取區,如果正在傳送,那麼等待發送緩衝區的資料傳送完畢,期間網路出錯返回SOCKET_ERROR.
  // 阻塞模式下:按上面檢查,recv收到資料完畢(協議會把一整個TCP包接收完畢,大包會重組後才算完畢)才返回,沒收到則一直等待。
  // 非阻塞模式下:按上面檢查,recv沒收到馬上返回不會阻塞,收到等接收完畢才返回。
  // 返回值小於0的SOCKET_ERROR檢查是否EAGAIN 接收期間網路斷開,非阻塞下沒有收到資料的返回10035錯誤碼。
  // 返回值等於0,表示對方socket已經正常斷開。
  // 返回值不等於請求的快取值,那麼再次接收。
  int nRecvRes = recv(sClient, szMessage, MSGSIZE, 0);  
  if(nRecvRes > 0)
  {
   szMessage[nRecvRes] = '\0';
   printf("Bytes receive : %s\n", szMessage);
  }
  else if(nRecvRes== 0 || (nRecvRes == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
  {
    printf("Connection Close.\n");
    break; // 呼叫closesocket避免四次揮手時候,主動關閉端一直在TIME_WAIT狀態,被動端在CLOSE_WAIT狀態。
  }
  else if(nRecvRes == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
  {
     // 非阻塞型別返回
    continue;
  }
  else
  {
   printf("Unknow recv error code: %d\n", WSAGetLastError());
    break; //  呼叫closesocket避免四次揮手時候,主動關閉端一直在TIME_WAIT狀態,被動端在CLOSE_WAIT狀態。
  }    
 }  
 // Clean up  
 closesocket(sClient);
 ReleaseWSAData();
 return 0;
}

1.select模型


#ifndef _SELECTMODEL_H_
#define  _SELECTMODEL_H_
#include <windows.h>
class SelectModel
{
public:
    static DWORD WINAPI  WorkerThread(LPVOID lpParameter);
    int Process();
};
#endif

/*
總結:第一個accept執行緒阻塞,第二個執行緒select,FD_ISSET非阻塞的等待時間來處理socket網路IO。
1. 第一執行緒accept會阻塞,只有一個服務端socket對應多個客戶端Socket,伺服器需要獲得客戶端的socket並可關閉它。
2. 第二執行緒select可以根據傳入時間,如果是NULL那麼是完全阻塞的,如果是0那麼是完全非阻塞的。
注意:處理大於64個的情況,在accept時候分組和開闢多個執行緒,或者是執行緒內分組,總是可以處理好的;
     沒有連線和延遲導致cpu不停空轉情況,雖然不會導致cpu 100%,但是也可以通過延遲sleep來避免或者乾脆不處理這種情況。
*/
#include "stdafx.h"
#include <winsock.h>
#include <stdio.h>
#include "SelectModel.h"
#define PORT       5150
#define MSGSIZE    8192
#pragma comment(lib, "ws2_32.lib")

int    g_iTotalConn = 0;
SOCKET g_CliSocketArr[FD_SETSIZE];// FD是File Describle檔案描述符,也就是socket檔案描述符(控制代碼)

//在大規模的網路連線方面,還是推薦使用IOCP或EPOLL模型.但是Select模型可以使用在諸如對戰類遊戲上,
//比如類似星際這種,因為它小巧易於實現,而且對戰類遊戲的網路連線量並不大.
//
//對於Select模型想要突破Windows 64個限制的話,可以採取分段輪詢,一次輪詢64個.例如套接字列表為128個,
//在第一次輪詢時,將前64個放入佇列中用Select進行狀態查詢,待本次操作全部結束後.將後64個再加入輪詢佇列中進行輪詢處理.
//這樣處理需要在非阻塞式下工作.以此類推,Select也能支援無限多個.

int SelectModel::Process()
{
    WSADATA     wsaData;
    SOCKET      sListen, sClient;
    SOCKADDR_IN local, client;
    int         iaddrSize = sizeof(SOCKADDR_IN);
    DWORD       dwThreadId;
    // Initialize Windows socket library
    ::WSAStartup(0x0202, &wsaData);

    // Create listening socket
    sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    // Bind
    local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    local.sin_family = AF_INET;
    local.sin_port = htons(PORT);

    int opt =  1;
    if ( setsockopt(sListen, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) < 0 )
    {
        printf("setsockopt Failed.\n");
        return false;
    }
    bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
    // Listen
    listen(sListen, 3);

    // Create worker thread
    CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
    while (TRUE)
    {

        // Accept a connection,接收到連線才存放到數組裡面,否則一直阻塞
        // 這裡決不能無條件的accept,伺服器應該根據當前的連線數來決定是否接受來自某個客戶端的連線。
        // 一種比較好的實現方案就是採用WSAAccept函式,而且讓WSAAccept回撥自己實現的Condition Function。
        sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
        printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
        // Add socket to g_CliSocketArr
        g_CliSocketArr[g_iTotalConn++] = sClient;
    }

    return 0;
}


 DWORD  SelectModel::WorkerThread(LPVOID lpParam)
{

    int            i = 0;
    fd_set         fdread;
    int            ret = 0;
    struct timeval tv = {1, 0};// 1是阻塞等待1秒鐘返回一次,後面的0是0毫秒
    char           szMessage[MSGSIZE];

    while (TRUE)
    {

        FD_ZERO(&fdread);//將fdread初始化空集
        for (i = 0; i < g_iTotalConn; i++)// 可以在這裡分段處理64個,用以支援多於64個的連線select.
        {

            FD_SET(g_CliSocketArr[i], &fdread);//將要檢查的套介面加入到集合中
        }
        // We only care read event
        // int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
        /*maxfdp是一個整數值,是指集合中所有檔案描述符的範圍,即所有檔案描述符的最大值加1,不能錯!
        在Windows中這個引數的值無所謂,可以設定不正確*/
        /*readfds:select監視的可讀檔案控制代碼集合。
        writefds: select監視的可寫檔案控制代碼集合。
        exceptfds:select監視的異常檔案控制代碼集合。*/
        /*struct timeval* timeout是select的超時時間,這個引數至關重要,它可以使select處於三種狀態,
        第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,一定等到監視檔案描述符集合中某
        個檔案描述符發生變化為止;
        第二,若將時間值設為0秒0毫秒,就變成一個純粹的非阻塞函式,不管檔案描述符是否有變化,都立刻返回繼續執行,
        檔案無變化返回0,有變化返回一個正值;
        第三,timeout的值大於0,這就是等待的超時時間,即 select在timeout時間內阻塞,超時時間之內有事件到來就返回了,
        否則在超時後不管怎樣一定返回,返回值同上述。
        故select函式的特性使得select模型可以是阻塞模型或非阻塞模型或者會進行等待的非阻塞模型,accept和select分離*/
        ret = select(MSGSIZE + 1, &fdread, NULL, NULL, &tv);//每隔一段時間,檢查可讀性的套介面,將可讀的拷貝到fdread裡面
        if (ret == 0)
        {
            // Time expired
            continue;
        }
        // select模型需要兩次遍歷列表,這是select模型效率較低的原因,高效能還是推薦IOCP或EPOLL模型。
        // 但是它易於實現,在小型的少連線數量情景下,例如小型對戰遊戲類似星際爭霸遊戲可以使用。
        for (i = 0; i < g_iTotalConn; i++)
        {
            if (FD_ISSET(g_CliSocketArr[i], &fdread))//如果可讀
            {
                // A read event happened on g_CliSocketArr
                ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0);
                if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
                {

                    // Client socket closed
                    printf("Client socket %d closed.\n", g_CliSocketArr[i]);
                    closesocket(g_CliSocketArr[i]);
                    if (i < g_iTotalConn - 1)
                    {           
                        g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];// 將最後面的移動到刪除的位置
                    }
                }
                else
                {
                    // We received a message from client
                    szMessage[ret] = '\0';
                    send(g_CliSocketArr[i], szMessage, strlen(szMessage), 0);
                }
            }// 可讀
        }// for
    }// while
  return 0;
}

2.非同步選擇WSAAsyncSelect模型


// 總結:就在一個訊息執行緒中,客戶端socket描述符和hWnd的訊息事件關聯。WSAAsyncSelect關聯FD訊息和關聯FD事件。.
//1. 一個伺服器socket對應多個客戶端socket,message是socket事件,wParam是socket客戶端FD控制代碼可以對其關閉控制代碼;lParam是FD事件
//2. WSAAsyncSelect在accept前註冊了非同步Select的Socket描述符/訊息和事件,當TCP/IP協議層指定事件時才用Msg通知給應用程式。
//3 在accept接收後,用WSAAsyncSelect觸發關心的Socket描述符和非同步事件,FD_READ和FD_CLOSE。

#include "stdafx.h"
#include "WinMainNetDataTransferModel.h"

#include <winsock.h>
#include <stdio.h>
#define WM_SOCKET WM_USER+0
#define PORT       5150
#define MSGSIZE    8192
#define WM_SOCKET WM_USER+0
#pragma comment(lib, "ws2_32.lib")

LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
    static TCHAR szAppName[] = _T("AsyncSelect Model");
    HWND         hwnd ;
    MSG          msg ;
    WNDCLASS     wndclass ;

    wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
    wndclass.lpfnWndProc   = WndProc ;
    wndclass.cbClsExtra    = 0 ;
    wndclass.cbWndExtra    = 0 ;
    wndclass.hInstance     = hInstance ;
    wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
    wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
    wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
    wndclass.lpszMenuName  = NULL ;
    wndclass.lpszClassName = szAppName ;

    if (!RegisterClass(&wndclass))
    {
        MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;
        return 0 ;
    }

    hwnd = CreateWindow (szAppName,                  // window class name
        TEXT ("AsyncSelect Model"), // window caption
        WS_OVERLAPPEDWINDOW,        // window style
        CW_USEDEFAULT,              // initial x position
        CW_USEDEFAULT,              // initial y position
        CW_USEDEFAULT,              // initial x size
        CW_USEDEFAULT,              // initial y size
        NULL,                       // parent window handle
        NULL,                       // window menu handle
        hInstance,                  // program instance handle
        NULL) ;                     // creation parameters

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg) ;
        DispatchMessage(&msg) ;
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // message是socket事件,wParam是socket控制代碼;lParam是FD事件
    // WSAAsyncSelect是在建立時候,在accept事件之前進行註冊非同步事件,當收到TCP/IP層事件時候才返回應用層。
    WSADATA       wsd;
    static SOCKET sListen;
    SOCKET        sClient;
    SOCKADDR_IN   local, client;
    int           ret, iAddrSize = sizeof(client);
    char          szMessage[MSGSIZE];

    switch (message)
    {
    case WM_CREATE:
        // Initialize Windows Socket library
        WSAStartup(0x0202, &wsd);

        // Create listening socket
        sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        // Bind
        local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
        local.sin_family = AF_INET;
        local.sin_port = htons(PORT);
        bind(sListen, (struct sockaddr *)&local, sizeof(local));

        // Listen
        // 核心會在自己的程序空間裡維護一個佇列以跟蹤這些完成的連線但伺服器程序還沒有接手處理或正在進行的連線,小於30
        listen(sListen, 3);

        // Associate listening socket with FD_ACCEPT event
        // WSAAsyncSelect在accept前註冊了非同步Select的Socket描述符,當TCP/IP協議層發生非同步Select事件時候才用Msg通知給應用程式
        WSAAsyncSelect(sListen, hWnd, WM_SOCKET, FD_ACCEPT);
        return 0;

    case WM_DESTROY:
        closesocket(sListen);
        WSACleanup();
        PostQuitMessage(0);
        return 0;

    case WM_SOCKET:
        if (WSAGETSELECTERROR(lParam))
        {
            closesocket(wParam);
            break;
        }
       // 非同步通知事件發生時候TCP/IP層會發送訊息,無論是接收,讀取,還是關閉資訊
        switch (WSAGETSELECTEVENT(lParam))//取低位位元組,網路事件
        {
        case FD_ACCEPT:
            // Accept a connection from client
            sClient = accept(wParam, (struct sockaddr *)&client, &iAddrSize);

            // Associate client socket with FD_READ and FD_CLOSE event
            // 在accept接收後,觸發關心的非同步事件,FD_READ和FD_CLOSE
            WSAAsyncSelect(sClient, hWnd, WM_SOCKET, FD_READ | FD_CLOSE);
            break;

        case FD_READ:
            // 讀取網路訊息
            ret = recv(wParam, szMessage, MSGSIZE, 0);

            if (ret == 0 || ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)
            {
                closesocket(wParam);
            }
            else
            {
                szMessage[ret] = '\0';
                // 讀取的同時傳送,並沒有伺服器端做出事件後傳送FD_WRITE然後send
                send(wParam, szMessage, strlen(szMessage), 0);
            }
            break;

        case FD_CLOSE:
            closesocket(wParam);     
            break;
        }
        return 0;
    }

    return DefWindowProc(hWnd, message, wParam, lParam);
}

3.事件選擇WSAEventSelect模型


//http://www.cppblog.com/changshoumeng/articles/113441.html
#ifndef _ASYNCSELECTMODEL_H_
#define _ASYNCSELECTMODEL_H_
#include <windows.h>
class EventSelectModel
{
public:
    static DWORD WINAPI  WorkerThread(LPVOID lpParameter);
    int Process();
    static void Cleanup(int index);
};
#endif

/*
總結:兩個執行緒,客戶端的socket描述符和一個Event事件物件關聯,
      WSACreateEvent,WSAEventSelect,WSAWaitForMultipleEvents,WSAEnumNetworkEvents。
      1.都是一個伺服器socket,對應多個客戶端socket;用accept來返回客戶端的socket,其實是關閉客戶端的socket。
      2.是用兩個執行緒,accept執行緒會阻塞,WSACreateEvent()返回事件型別,且用WSAEventSelect關聯和檢測客戶端socket和事件。
      3.子執行緒是用WSAWaitForMultipleEvents檢測到客戶端socket事件(根據配置時間是限時的,當0時間是非阻塞的),
       且返回事件的id,通過index = ret - WSA_WAIT_EVENT_0獲取對應的客戶端socket;用WSAEnumNetworkEvents獲取事件型別進行操作。
*/
#include "stdafx.h"
//#include <WinUser.h>
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>
#define WM_SOCKET WM_USER+0
#define PORT       5150
#define MSGSIZE    8192
#pragma comment(lib, "ws2_32.lib")
#include "EventSelectModel.h"

int      g_iTotalConnNum = 0;
SOCKET   g_CliSocketVec[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventVec[MAXIMUM_WAIT_OBJECTS];

int EventSelectModel::Process()
{
    WSADATA     wsaData;
    SOCKET      sListen, sClient;
    SOCKADDR_IN local, client;
    DWORD       dwThreadId;
    int         iaddrSize = sizeof(SOCKADDR_IN);

    // Initialize Windows Socket library
    int reqErr = WSAStartup(0x0202, &wsaData);
    if(reqErr != 0)
    {
        printf("載入請求指定版本的windows socket api DLL 失敗.\n");
        return 0;
    }
    
    // Create listening socket
    sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(INVALID_SOCKET == sListen)
    {
        printf("Get Socket Error: INVALID_SOCKET.\n");
        return 0;
    }
    // Bind
    local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    local.sin_family = AF_INET;
    local.sin_port = htons(PORT);
    // 伺服器可以繫結任何的地址
    bind(sListen, (struct sockaddr *)&local, iaddrSize);
    // Listen
    // 同時連線的使用者數量太多,伺服器一下子不能處理那麼多,
    // 3是核心TCP佇列快取的同時收到未處理的連線數量超過了將會丟棄,一般小於30.
    listen(sListen, 3);
    // Create worker thread
    CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
    while (TRUE)
    {
        // Accept a connection
        // accept會阻塞本執行緒。
        sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
        printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

        // Associate socket with network event
        // 可以在這裡分組,每64個一組。
        g_CliSocketVec[g_iTotalConnNum] = sClient;//接受連線的套介面
        // 獲取事件,用於觸發網路事件訊息,訊息和事件關聯而不是和視窗關聯。
        g_CliEventVec[g_iTotalConnNum] = WSACreateEvent();//返回事件物件控制代碼

        //註冊關心的事件,在套介面上將一個或多個網路事件與事件物件關聯在一起。
        WSAEventSelect(g_CliSocketVec[g_iTotalConnNum],//套介面
            g_CliEventVec[g_iTotalConnNum],//事件物件
            FD_READ | FD_CLOSE);//網路事件
            g_iTotalConnNum++;
    }
}

DWORD EventSelectModel::WorkerThread(LPVOID lpParameter)
{
    int              ret, index;
    WSANETWORKEVENTS NetworkEvents;
    char             szMessage[MSGSIZE];

    while (TRUE)
    {
        //返回導致返回的事件物件
        // 可以分組處理捕獲事件,每64個一組。
        ret = WSAWaitForMultipleEvents(g_iTotalConnNum,//陣列中的控制代碼數目,最多可支援64個WSA_MAXIMUM_WAIT_EVENTS
            g_CliEventVec,//指向一個事件物件控制代碼陣列的指標
            FALSE, //TRUE都is signaled才返回;FALSE只有一個signaled都會返回,如果期間多個變signaled那麼返回最小的。
            1000, //超時間隔後返回,單位為毫秒,和上面的引數是或的關係都會返回。
            FALSE);//是否執行完成例程,如果是完成IO,那麼直接返回儘管沒有is signaled的socket;如果FALSE那麼不這樣處理。
        if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
        {
            continue;
        }

        index = ret - WSA_WAIT_EVENT_0;
        //在套介面上查詢與事件物件關聯的網路事件
        WSAEnumNetworkEvents(g_CliSocketVec[index], g_CliEventVec[index], &NetworkEvents);
        //處理FD-READ網路事件
        if (NetworkEvents.lNetworkEvents & FD_READ)
        {
            // Receive message from client
            ret = recv(g_CliSocketVec[index], szMessage, MSGSIZE, 0);
            if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
            {
                Cleanup(index);
            }
            else
            {
                szMessage[ret] = '\0';
                // 收到馬上傳送
                send(g_CliSocketVec[index], szMessage, strlen(szMessage), 0);
            }
        }
        //處理FD-CLOSE網路事件
        if (NetworkEvents.lNetworkEvents & FD_CLOSE)
        {
            Cleanup(index);
        }
    }

    return 0;
}

void EventSelectModel::Cleanup(int index)
{
    closesocket(g_CliSocketVec[index]);
    WSACloseEvent(g_CliEventVec[index]);

    if (index < g_iTotalConnNum - 1)
    {
        g_CliSocketVec[index] = g_CliSocketVec[g_iTotalConnNum - 1];
        g_CliEventVec[index] = g_CliEventVec[g_iTotalConnNum - 1];
    }
    g_iTotalConnNum--;
}

4.重疊IO Overlapped IO模型

1)通過事件物件實現重疊IO模型


#ifndef _OVERLAPPINGIOBYEVENT_H
#define _OVERLAPPINGIOBYEVENT_H
#include <windows.h>
class OverlappingIOByEvent
{
public:
    static DWORD WINAPI WorkerThread(LPVOID);
    static void Cleanup(int index);
    int Process();

};
#endif

/*
1.啟用重疊IO,WSASocket函式,ReadFile()函式,WriteFile()函式設定類似FILE_FLAG_OVERLAPPED標誌,
或者預設winsocket2 socket是重疊IO的;WSASend,WSARecv,WSAIoctl函式,傳入WSAOVERLAPPED標記則也是重疊IO的。
2.訊號回撥機制,用WSAEVENT事件物件和socket描述符關聯,實現回撥通知;WSARecv設定完標記可以馬上返回,故是"非同步的"。
3.重疊機制,有重疊IO的函式,會馬上返回,在底層為這個IO開啟一個執行緒,故可以同時進行多個不同的IO操作,故是"重疊的"。
重疊IO函式:事件:WSACreateEvent,WSAResetEvent,WSACloseEvent函式。
            IO函式:WSAOVERLAPPED,WSABUF結構體;WSARecv,WSAWaitForMultipleEvents,WSAGetOverlappedResult函式。
優點:非同步的,重疊的,通過完成回撥得到結果呼叫效率更高,可以去做其它事情;
      如果WSARecv開始那麼直接將資料拷貝到應用程式,不用拷貝到TCP/UDP快取效率更高。
缺點:受到64個限制,重疊IO還是需要給每個socket生成一個執行緒,成千上萬的連線,需要消耗很多的執行緒切換計算,效率低下。
*/
#include "stdafx.h"
#include <winsock2.h>
#include <stdio.h>
#include "OverlappingIOByEvent.h"

#define PORT    5150
#define MSGSIZE 8192

#pragma comment(lib, "ws2_32.lib")


typedef struct
{
    WSAOVERLAPPED overlap;
    WSABUF        Buffer;
    char          szMessage[MSGSIZE];
    DWORD         NumberOfBytesRecvd;
    DWORD         Flags;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

static int                     g_iTotalConn = 0;
static SOCKET                  g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
static WSAEVENT                g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
static LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];// IO操作的資料 WSAOVERLAPPED包含了指標和事件

int OverlappingIOByEvent::Process()
{
    WSADATA     wsaData;
    SOCKET      sListen, sClient;
    SOCKADDR_IN local, client;
    DWORD       dwThreadId;
    int         iaddrSize = sizeof(SOCKADDR_IN);

    // Initialize Windows Socket library
    WSAStartup(0x0202, &wsaData);

    // Create listening socket
    sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // Bind
    local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    local.sin_family = AF_INET;
    local.sin_port = htons(PORT);
    bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

    // 核心會在自己的程序空間裡維護一個佇列以跟蹤這些完成的連線但伺服器程序還沒有接手處理或正在進行的連線,小於30
    listen(sListen, 3);

    // Create worker thread
    CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);

    while (TRUE)
    {
        // Accept a connection,accept是阻塞的
        sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
        printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

        g_CliSocketArr[g_iTotalConn] = sClient;// 獲取客戶端socket

        // Allocate a PER_IO_OPERATION_DATA structure
        g_pPerIODataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(
            GetProcessHeap(),
            HEAP_ZERO_MEMORY,
            sizeof(PER_IO_OPERATION_DATA));

        // buffer是包含了網路資料的結構體,有len和buf
        g_pPerIODataArr[g_iTotalConn]->Buffer.len = MSGSIZE;
        g_pPerIODataArr[g_iTotalConn]->Buffer.buf = g_pPerIODataArr[g_iTotalConn]->szMessage;

        // 獲取事件,賦值給事件物件和賦值給IO結構體,事件物件和OVERLAPPED事件物件都指向相同的地方,且WSARecv中和事件物件關聯
        g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();

        // Launch an asynchronous operation
        // WSARecv非同步操作
        //進行接收資料檢查設定,是非阻塞的,g_pPerIODataArr[g_iTotalConn]->overlap.hEvent事件和socket描述符關聯
        int nResCode = WSARecv(
            g_CliSocketArr[g_iTotalConn],// 檢查的socket描述符
            &g_pPerIODataArr[g_iTotalConn]->Buffer,// 賦值,WSABuffer包含了快取指標和快取長度
            1, // buffer count
            &g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd,// 賦值,接收的快取位元組數
            &g_pPerIODataArr[g_iTotalConn]->Flags,// 返回用於修改WSARecv 表現的標誌,在WSAGetOverlappedResult用
            // OVERLAPPED結構體包含了底層服務需要的socket控制代碼類似檔案控制代碼,socket控制代碼的偏移和指標,
            // 主要是 HANDLE  hEvent,當函式lpCompletionRoutine回撥函式為空的時候,需要提供一個合法的WSAEVENT或者NULL值。
            &g_pPerIODataArr[g_iTotalConn]->overlap,// 當lpCompletionRoutine不為空的時候,hEvent的值沒有什麼要求。
            NULL);// lpCompletionRoutine 完成例程回撥函式,當接收IO操作完成的時候。
        // 如果lpOverlapped是NULL,lpCompletionRoutine是NULL,那麼就是一個非重疊的IO模型,和普通recv函式一樣。
        if(nResCode == SOCKET_ERROR)
        {
            if(WSAGetLastError() != WSA_IO_PENDING)
            {
                printf("WSARecv error code:%d", WSAGetLastError());
                return -1;
            }
        }
        else if(nResCode == 0)
        {
            /*If no error occurs and the receive operation has completed immediately,
            WSARecv returns zero. In this case,
            the completion routine will have already been scheduled to be called once the calling
            thread is in the alertable state. */
            printf("WSARecv OK");
        }

        g_iTotalConn++;
    }

    closesocket(sListen);
    WSACleanup();
    return 0;
}

DWORD OverlappingIOByEvent::WorkerThread(LPVOID)
{
    int   ret, index;
    // 傳輸的位元組數
    DWORD cbTransferred;

    while (TRUE)
    {
        //返回導致返回的事件物件,在得到訊號量通知或者等待時間到後返回,是半非阻塞的;接收資料和得到訊號。
        ret = WSAWaitForMultipleEvents(g_iTotalConn,
            g_CliEventArr, // 檢測的事件陣列
            FALSE, // TRUE都is signaled才返回;FALSE只有一個signaled都會返回
            1000,// 超時間隔後返回,單位為毫秒,和上面的引數是或的關係都會返回
            FALSE);// 是否執行完成例程,如果是完成IO,那麼直接返回儘管沒有is signaled的socket;如果FALSE那麼不這樣處理。
        if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
        {
            //printf(L"WSAWaitForMultipleEvents failed with error: %d\n", WSAGetLastError());
            continue;
        }

        index = ret - WSA_WAIT_EVENT_0;
        // 得到事件訊號後,重新設定事件物件為沒有訊號
        WSAResetEvent(g_CliEventArr[index]);

        // 得到重疊IO事件返回的結果
        WSAGetOverlappedResult(
            g_CliSocketArr[index],// socket描述符
            &g_pPerIODataArr[index]->overlap, // OVERLAPPED結構體,提供給底層用,會根據WASRecv設定的網路buffer資料.
            &cbTransferred, // 傳輸的資料大小
            TRUE, // TRUE重疊IO操作完成才返回,只有是基於事件的重疊IO才設定為TRUE.
            &g_pPerIODataArr[g_iTotalConn]->Flags); // 重疊IO操作的型別, 來自於WSARecv or WSARecvFrom

        if (cbTransferred == 0)
        {
            // The connection was closed by client
            Cleanup(index);
        }
        else
        {
            // g_pPerIODataArr[index]->szMessage contains the received data
            // g_pPerIODataArr[g_iTotalConn]->Buffer在WSARecv中指定了,WSAGetOverlappedResult會給其賦值。
            g_pPerIODataArr[index]->szMessage[cbTransferred] = '\0';
            // 傳送資料給客戶端
            send(g_CliSocketArr[index], g_pPerIODataArr[index]->szMessage,cbTransferred, 0);
            // 接收訊息的長度進行重新設定
            g_pPerIODataArr[index]->Buffer.len = MSGSIZE;
            g_pPerIODataArr[index]->Buffer.buf = g_pPerIODataArr[index]->szMessage;  

            // Launch another asynchronous operation
            // WSARecv非同步操作
            // 再進行接收資料檢查設定,是非阻塞的,g_pPerIODataArr[g_iTotalConn]->overlap.hEvent事件被WSAResetEvent重置了
            // 事件物件和socket描述符關聯。
            WSARecv(
                g_CliSocketArr[index],
                &g_pPerIODataArr[index]->Buffer,
                1, // buffer count
                &g_pPerIODataArr[index]->NumberOfBytesRecvd,
                &g_pPerIODataArr[index]->Flags,
                &g_pPerIODataArr[index]->overlap,
                NULL);
        }
    }

    return 0;

}

void OverlappingIOByEvent::Cleanup(int index)
{
    closesocket(g_CliSocketArr[index]);// socket描述符
    WSACloseEvent(g_CliEventArr[index]); // 關閉事件物件
    HeapFree(GetProcessHeap(), 0, g_pPerIODataArr[index]);// 釋放申請的重疊IO資料,結構體記憶體

    if (index < g_iTotalConn - 1)// 陣列下標從0開始
    {
        g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];// 前移
        g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
        g_pPerIODataArr[index] = g_pPerIODataArr[g_iTotalConn - 1];// 存放的是指標,所以也可以交給前面位置
    }

    g_pPerIODataArr[--g_iTotalConn] = NULL;// 指向NULL,那塊記憶體交給了g_pPerIODataArr[index]管理
}

2)通過回撥函式實現重疊IO模型

#ifndef _OVERLAPPEDIOBYCOMPLETIONROUTINE_H
#define _OVERLAPPEDIOBYCOMPLETIONROUTINE_H
#include <Windows.h>

class OverlappedIOByCompletionRoutine
{
public:
    int Process();
    static DWORD WINAPI WorkerThread(LPVOID);
};
#endif

/*
1.非同步的和重疊的,用重疊IO實現的,重疊IO會給WSARecv的socket開闢的一個新執行緒內去做。
2.完成例程,重疊IO完成的回撥函式,這個時候在WSARecv完成後會給WSAOVERLAPPED的快取塊賦值。
使用函式:WSARecv,CompletionRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD)回撥函式,回撥函式後才會賦值。
優點:非同步的,重疊的,通過完成回撥得到結果呼叫效率更高,可以去做其它事情;
     如果WSARecv開始那麼直接將資料拷貝到應用程式,不用拷貝到TCP/UDP快取效率更高。
     不再受到64個限制,沒有連線子執行緒空轉的情況下sleep一下。
缺點:重疊IO還是需要給每個socket生成一個執行緒,成千上萬的連線,需要消耗很多的執行緒切換計算,效率低下;
      完全埠可以用執行緒池來解決。

*/
#include "stdafx.h"
#include <stdio.h>
#include <WINSOCK2.H>
#pragma comment(lib, "ws2_32.lib")

#include "OverlappedIOByCompletionRoutine.h"

#define PORT    5150
#define MSGSIZE 8192

SOCKET g_sNewClientConnection;

BOOL   g_bNewConnectionArrived = FALSE;
typedef struct
{
    WSAOVERLAPPED overlap;
    WSABUF        Buffer;
    char          szMessage[MSGSIZE];
    DWORD         NumberOfBytesRecvd;
    DWORD         Flags;
    SOCKET        sClient;
}PER_IO_OPERATION_DATA2, *LPPER_IO_OPERATION_DATA2;

void CALLBACK CompletionRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);

int OverlappedIOByCompletionRoutine::Process()
{
    WSADATA     wsaData;
    SOCKET      sListen;
    SOCKADDR_IN local, client;
    DWORD       dwThreadId;
    int         iaddrSize = sizeof(SOCKADDR_IN);

    // Initialize Windows Socket library
    WSAStartup(0x0202, &wsaData);

    // Create listening socket
    sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // Bind
    local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    local.sin_family = AF_INET;
    local.sin_port = htons(PORT);
    bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

    // 核心會在自己的程序空間裡維護一個佇列以跟蹤這些完成的連線但伺服器程序還沒有接手處理或正在進行的連線,小於30
    listen(sListen, 3);

    // Create worker thread
    CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);

    while (TRUE)
    {
        // Accept a connection
        // 阻塞的接收資訊
        g_sNewClientConnection = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
        g_bNewConnectionArrived = TRUE;
        printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    }
}

DWORD OverlappedIOByCompletionRoutine::WorkerThread(LPVOID lpParam)
{
    LPPER_IO_OPERATION_DATA2 lpPerIOData = NULL;
    while (TRUE)
    {
        if (g_bNewConnectionArrived)
        {
            // Launch an asynchronous operation for new arrived connection
            lpPerIOData = (LPPER_IO_OPERATION_DATA2)HeapAlloc(
                GetProcessHeap(),
                HEAP_ZERO_MEMORY,
                sizeof(PER_IO_OPERATION_DATA2));
            lpPerIOData->Buffer.len = MSGSIZE;
            lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
            lpPerIOData->sClient = g_sNewClientConnection;

            // 接收連線,還沒收到資料,進行重疊IO非同步的接收資料檢查設定,馬上返回,接收完進行CompletionRoutine回撥函式
            WSARecv(lpPerIOData->sClient,
                &lpPerIOData->Buffer,
                1,
                &lpPerIOData->NumberOfBytesRecvd,
                &lpPerIOData->Flags,
                &lpPerIOData->overlap,
                CompletionRoutine);     

            g_bNewConnectionArrived = FALSE;
        }

        SleepEx(1000, TRUE);
    }
    return 0;
}

void CALLBACK CompletionRoutine(DWORD dwError,
                                DWORD cbTransferred,
                                LPWSAOVERLAPPED lpOverlapped,
                                DWORD dwFlags)
{
    LPPER_IO_OPERATION_DATA2 lpPerIOData = (LPPER_IO_OPERATION_DATA2)lpOverlapped;

    if (dwError != 0 || cbTransferred == 0)
    {
        // Connection was closed by client
        closesocket(lpPerIOData->sClient);
        HeapFree(GetProcessHeap(), 0, lpPerIOData);
    }
    else
    {
        // 得到回撥函式,返回的資料
        lpPerIOData->szMessage[cbTransferred] = '\0';
        send(lpPerIOData->sClient, lpPerIOData->szMessage, cbTransferred, 0);

        // Launch another asynchronous operation
        // 重新設定訊號量,
        memset(&lpPerIOData->overlap, 0, sizeof(WSAOVERLAPPED));

        // 接收訊息的長度進行重新設定
        lpPerIOData->Buffer.len = MSGSIZE;
        lpPerIOData->Buffer.buf = lpPerIOData->szMessage;   
        
        // 重新進行重疊IO非同步的接收資料檢查設定,馬上返回,接收完進行CompletionRoutine回撥函式
        WSARecv(lpPerIOData->sClient,
            &lpPerIOData->Buffer,
            1,
            &lpPerIOData->NumberOfBytesRecvd,
            &lpPerIOData->Flags,
            &lpPerIOData->overlap,
            CompletionRoutine);
    }
}

5.完成埠 Completion Port模型

#ifndef IOCOMPLETIONPORT_H
#define IOCOMPLETIONPORT_H
class IOCompletionPort
{
public:
    int Process();
    static DWORD WINAPI WorkerThread(LPVOID CompletionPortID);
};
#endif

/*
優點:非同步的:同步和非同步是多個任務一個個去做,還是同時做多件事情,I/O分同步和非同步的IO.
      非阻塞的:非阻塞和阻塞是一個任務是被這個任務阻塞了,還是不會被阻塞,類似accept函式值阻塞的
      在阻塞或非阻塞上面是可以非同步或者同步來做的。

完全埠的機制:
1.完全埠佇列事件通知機制(重疊IO結構體,內部的IO請求完成才返回,否則在內部等待,外部的執行緒非阻塞可以做自己的事情)。
2.執行緒池機制(完全埠訊息佇列;外部幾個執行緒從完全埠佇列獲取訊息,外部的執行緒也可以喚醒和掛起;
內部socket由多個IO執行緒和多個socket對應,執行緒池的執行緒可喚醒和掛起)。

一個完成埠其實就是一個通知佇列,由作業系統把已經完成的重疊I/O請求的通知放入其中。當某項I/O操作一旦完成,
某個可以對該操作結果進行處理的工作者執行緒就會收到一則通知。完成埠建立後,用CreateIoCompletionPort,把完成埠和套接字關聯起來。
在建立了完成埠、將一個或多個套接字與之相關聯之後,我們就要建立若干個執行緒來處理完成通知。
這些執行緒不斷迴圈呼叫GetQueuedCompletionStatus ()函式並返回完成通知。

1)AcceptEx()執行緒數量,AcceptEx需要SO_CONNECT_TIME選項避免超時:
我們要設計一個伺服器來響應客戶端的連線、傳送請求、接收資料以及斷開連線。那麼,伺服器將需要建立一個監聽套接字,
把它與某個完成埠進行關聯,為每顆CPU建立一個工作執行緒。再建立一個執行緒專門用來發出AcceptEx()。
我們知道客戶端會在發出連線請求後立刻傳送資料,所以如果我們準備好接收緩衝區會使事情變得更為容易。
當然,不要忘記不時地輪詢AcceptEx()呼叫中使用的套接字(使用SO_CONNECT_TIME選項引數)來確保沒有惡意超時的連線。

該設計中有一個重要的問題要考慮,我們應該允許多少個AcceptEx()進行守候。這是因為,每發出一個AcceptEx()時我們都同時需要為
它提供一個接收緩衝區,那麼記憶體中將會出現很多被鎖定的頁面(前文說過了,每個重疊操作都會消耗一小部分未分頁記憶體池,
同時還會鎖定所有涉及的緩衝區)。這個問題很難回答,沒有一個確切的答案。最好的方法是把這個值做成可以調整的,
通過反覆做效能測試,你就可以得出在典型應用環境中最佳的值。
2)併發數量:
好了,當你測算清楚後,下面就是傳送資料的問題了,考慮的重點是你希望伺服器同時處理多少個併發的連線。通常情況下,
伺服器應該限制併發連線的數量以及等候處理的傳送呼叫。因為併發連線數量越多,所消耗的未分頁記憶體池也越多;
等候處理的傳送呼叫越多,被鎖定的記憶體頁面也越多(小心別超過了極限)。這同樣也需要反覆測試才知道答案。

開發大響應規模的Winsock伺服器並不是很可怕,其實也就是設定一個監聽套接字、接受連線請求和進行重疊收發呼叫。
通過設定合理的進行守候的重疊呼叫的數量,防止出現未分頁記憶體池被耗盡,這才是最主要的挑戰。

參考:
http://6265510.blog.51cto.com/6255510/1078740
https://software.intel.com/zh-cn/blogs/2011/02/16/socket-iocp
http://www.cnblogs.com/flying_bat/archive/2006/09/29/517987.html

*/
#include "stdafx.h"
#include <WINSOCK2.H>
#include <stdio.h>
#include "IOCompletionPort.h"

#define PORT    5150
#define MSGSIZE 8192

#pragma comment(lib, "ws2_32.lib")

typedef enum
{
    RECV_POSTED
}OPERATION_TYPE;

typedef struct
{
    WSAOVERLAPPED  overlap;
    WSABUF         Buffer;
    char           szMessage[MSGSIZE];
    DWORD          NumberOfBytesRecvd;
    DWORD          Flags;
    OPERATION_TYPE OperationType;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

int IOCompletionPort::Process()
{
    WSADATA                 wsaData;
    SOCKET                  sListen, sClient;
    SOCKADDR_IN             local, client;
    DWORD                   i, dwThreadId;
    int                     iaddrSize = sizeof(SOCKADDR_IN);
    HANDLE                  CompletionPort = INVALID_HANDLE_VALUE;
    SYSTEM_INFO             systeminfo;
    LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

    // Initialize Windows Socket library
    WSAStartup(0x0202, &wsaData);

    // Create completion port
    /*HANDLE WINAPI CreateIoCompletionPort(
        _In_      HANDLE FileHandle,
        _In_opt_  HANDLE ExistingCompletionPort,
        _In_      ULONG_PTR CompletionKey,
        _In_      DWORD NumberOfConcurrentThreads
        );*/
    // 獲取系統分配的完全埠號,用INVALID_HANDLE_VALUE,0,0,0引數。
    CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

    // Create worker thread,根據CPU數量來建立多少個執行緒
    GetSystemInfo(&systeminfo);
    for (i = 0; i < systeminfo.dwNumberOfProcessors; i++)
    {
        CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId);
    }

    // Create listening socket
    // winsocket2預設的socket預設是支援重疊IO的
    sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // Bind
    local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    local.sin_family = AF_INET;
    local.sin_port = htons(PORT);
    bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

    // 核心會在自己的程序空間裡維護一個佇列以跟蹤這些完成的連線但伺服器程序還沒有接手處理或正在進行的連線,小於30
    listen(sListen, 3);

    while (TRUE)
    {
        // Accept a connection,會阻塞,但是效能高有才處理
        sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
        printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

        // Associate the newly arrived client socket with completion port
        // 將新建立連線的socket控制代碼和完全埠關聯,NumberOfConcurrentThreads為空那麼系統會用當前CPU數量。
        CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);

        // Launch an asynchronous operation for new arrived connection
        lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
            GetProcessHeap(),
            HEAP_ZERO_MEMORY,
            sizeof(PER_IO_OPERATION_DATA));

        lpPerIOData->Buffer.len = MSGSIZE;
        lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
        lpPerIOData->OperationType = RECV_POSTED;

        // 註冊檢查接收函式,將重疊IO事件和socket關聯,非阻塞函式;內部用了事件通知和執行緒池
        WSARecv(sClient,// 進來連線的client
            &lpPerIOData->Buffer,// 接收的快取
            1, // buffer個數
            &lpPerIOData->NumberOfBytesRecvd,// 接收到的位元組數
            &lpPerIOData->Flags,// 用於獲取修改操作WSARecv的標誌位
            &lpPerIOData->overlap,// 重疊IO事件
            NULL);// 回撥函式沒有
    }
    /*BOOL WINAPI PostQueuedCompletionStatus(
        _In_      HANDLE CompletionPort,
        _In_      DWORD dwNumberOfBytesTransferred,
        _In_      ULONG_PTR dwCompletionKey,
        _In_opt_  LPOVERLAPPED lpOverlapped
        );*/
    // 退出完全埠,傳遞-1大小給完全埠,重疊IO結構體為0
    PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL);
    CloseHandle(CompletionPort);
    closesocket(sListen);
    WSACleanup();
    return 0;
}

DWORD IOCompletionPort::WorkerThread(LPVOID CompletionPortID)
{
    HANDLE                  CompletionPort=(HANDLE)CompletionPortID;
    DWORD                   dwBytesTransferred;
    SOCKET                  sClient;
    LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

    while (TRUE)
    {
        
        // 從IO完全埠佇列獲取socket IO操作完成後的資料;INFINITE引數也會導致阻塞,但是有才喚醒效能高
        GetQueuedCompletionStatus(
            CompletionPort,// 完成埠值
            &dwBytesTransferred,// 接收的傳遞資料位元組數
            (PULONG_PTR)&sClient, // 完全埠鍵值,也就是客戶端的socket控制代碼
            (LPOVERLAPPED *)&lpPerIOData, // 完全埠的重疊IO資料
            INFINITE);// 等待的時間,如果INFINITE那麼只有完全埠佇列有接收完成的socket才返回
        if (dwBytesTransferred == 0xFFFFFFFF)
        {
            return 0;
        }

        if (lpPerIOData->OperationType == RECV_POSTED)
        {
            if (dwBytesTransferred == 0)
            {
                // Connection was closed by client
                closesocket(sClient);
                HeapFree(GetProcessHeap(), 0, lpPerIOData);       
            }
            else
            {
                // 得到的資料是WSARecv中指定的,lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
                lpPerIOData->szMessage[dwBytesTransferred] = '\0';
                // 傳送可以用支援重疊IO的函式WSASend
                send(sClient, lpPerIOData->szMessage, dwBytesTransferred, 0);

                // Launch another asynchronous operation for sClient
                memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));

                lpPerIOData->Buffer.len = MSGSIZE;
                lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
                lpPerIOData->OperationType = RECV_POSTED;

                // 註冊檢查接收函式,將重疊IO事件和socket關聯,非阻塞函式
                WSARecv(sClient,
                    &lpPerIOData->Buffer,
                    1,
                    &lpPerIOData->NumberOfBytesRecvd,
                    &lpPerIOData->Flags,
                    &lpPerIOData->overlap,
                    NULL);
            }
        }
    }
    return 0;
}