windows上的5种网络通信模型示例代码

一些好设计的经验:

linux网络:

高性能网络编程IO复用和Epoll高效率之处-遍历的集合更小空间换时间/水平触发和边缘触发主动返回。

反应堆的设计模式-避免C风格的一个应用逻辑都需要处理多个对象而是用OO设计模式方式分离。

windows网络:

select模型,WSAAsyncSelect模型,WSAEventSelect模型,重叠Overlapped IO模型,完全端口IO Completion Port模型。

是遵循定期检查,窗口事件通知,事件对象通知,多线程重叠IO和事件对象完成通知,事件对象完成通知和通过完成端口消息队列有效的管理外部和内部的线程池,进化来提高网络通信模型。

0.客户端设计
#include "stdafx.h"
#include  
#include  
#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
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
#include
#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
#include
#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
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
#include
#include
#include
#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
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
#include
#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

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
#include
#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
#include
#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;
}

你可能感兴趣的:(网络编程)