Windows下异步IO模型详解

1.选择模型

1.1选择模型介绍

  • 选择(select)模型是Winsock中最常见的 I/O模型。核心便是利用 select 函数,实现对 I/O的管理。
  • 利用 select 函数来判断某Socket上是否有数据可读,或者能否向一个套接字写入数据,防止程序在Socket处于阻塞模式中时。在一次 I/O 调用(如send或recv、accept等)过程中,被迫进入“锁定”状态。
  • 可以同时等待多个套接字,当某个或者多个套接字满足可读写条件时,通知应用程序调用输入或者输出函数进行读写。
  • 同时防止在套接字处于非阻塞模式中时,产生WSAEWOULDBLOCK错误。它意味着请求的操作在调用期间没有时间完成。举个例子来说,假如在系统的输入缓冲区中,尚不存在“待决”的数据,那么recv(接收数据)调用就会返回WSAEWOULDBLOCK错误。通常,我们需要重复调用同一个函数,直至获得一个成功返回代码。 

1.2select函数介绍

int select(
  __in          int nfds,//参数会被忽略
  __in_out      fd_set* readfds,//检查可读性
  __in_out      fd_set* writefds,//检查可写性
  __in_out      fd_set* exceptfds,//检查例外数据
  __in          const struct timeval* timeout//空指针计数器
);

1.2.1fd_set 结构

typedef struct fd_set { 
 u_int fd_count;
 SOCKET fd_array[FD_SETSIZE];
} fd_set;
#define FD_SETSIZE      64
所以 fd_set 结构中最多只能监视64个套接字。

fdset 代表着一系列特定套接字的集合。
其中, readfds 集合包括符合下述任何一个条件的套接字:

● 有数据可以读入。
● 连接已经关闭、重设或中止。

● 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。

writefds 集合包括符合下述任何一个条件的套接字:
● 有数据可以发出。

● 如果已完成了对一个非锁定连接调用的处理,连接就会成功。

exceptfds 集合包括符合下述任何一个条件的套接字:
● 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。

● 有带外(Out-of-band,OOB)数据可供读取。

1.2.2timeval结构

  • tv_sec 字段以秒为单位指定等待时间。
  • tv_usec 字段则以毫秒为单位指定等待时间。
  • 1秒 = 1000毫秒。
  • 若将超时值设置为(0 , 0),表明 select 会立即返回,出于对性能方面的考虑,应避免这样的设置

1.2.3select函数返回值

select 成功完成后,会在 fdset 结构中返回刚好有未完成的 I/O操作的所有套接字句柄的总量。若超过 timeval 设定的时间,便会返回0。若 select 调用失败,都会返回 SOCKET_ERROR,应该调用 WSAGetLastError 获取错误码!

1.2.4select函数用法

Winsock 提供了下列宏操作,可用来针对 I/O活动,对 fdset 进行处理与检查:

  • FD_CLR(s, *set):从set中删除套接字s。
  • FD_ISSET(s, *set):检查s是否set集合的一名成员;如答案是肯定的是,则返回TRUE。
  • FD_SET(s, *set):将套接字s加入集合set。
  • FD_ZERO( * set):将set初始化成空集合

select操作套接字句柄的过程如下:

  1. 使用FDZERO宏,初始化一个fdset对象。
  2. 使用FDSET宏,将套接字句柄加入到fdset集合中。
  3. 调用 select 函数,等待其返回……select 完成后,会返回在所有 fdset 集合中设置的套接字句柄总数,并对每个集合进行相应的更新。
  4. 根据 select的返回值和 FDISSET宏,对 fdset 集合进行检查。
  5. 知道了每个集合中“待决”的 I/O操作之后,对 I/O进行处理,然后返回步骤1,继续进行 select 处理。

1.3选择模型的优势与不足

优势:

  1. 可以同时对多个建立起来的套接字进行有序的管理。可以防止应用程序在一次I/O调用过程中,使阻塞模式套接字被迫进入阻塞状态;使非阻塞套接字产生WASEWOUBDBLOCK错误。
  2. selcet()函数好像就是一个消息中心,当消息到来时,通知应用程序接收和发送数据。这使得Windows Sockets应用程序开发人员可以把精力更多集中在如何处理数据的发送和接收上。
不足:

当完成一次I/O操作经历了两次Windows Sockets函数的调用。例如,当接收对方数据时,第一步,调用selcet()函数等待该套接字的满足条件。第二步,调用recv()函数接收数据。这种结果与一个阻塞模式的套接字上调用recv()函数是一样的。因此,使用select()函数的Windows Sockets程序,其效率可能受损。因为,每一个Windows Sockets I/O调用都会经过该函数,因而会导致严重的CPU额外负担。在CPU使用效率不是关键因素时,这种效率可以接受。但是,当需要高效率时,肯定会产生问题。

1.4代码实现

#pragma once

#include <winsock2.h>
#include <list>
#pragma comment(lib, "ws2_32.lib")

#define _DEFAULTPORT  1234
#define MAXNUM        10 
#define UM_DATA    WM_USER + 1

using namespace std;

enum NetType{NT_READ,NT_WRITE};

class CINet
{
public:
    CINet(void);
    ~CINet(void);
public:
    //1.初始化网络
    bool InitNetWork(HWND hwnd);
    //2.卸载网络
    void UnInitNetWork();
    //3.发送数据
    bool SendData(char *szContent,int nLen);
    //接收客户端连接
    static DWORD WINAPI ThreadAccept(void*);
     static DWORD WINAPI ThreadRecv(void*);
    //获得本机iP
    static long GetValidIp()
    {
        char szhostname[100] = {0};
        hostent *remoteHost = NULL;
        in_addr addr;
        //1.获得本机的名称
        if(!gethostname(szhostname,sizeof(szhostname)))
        {
              //2.通过名称获得IP地址
              remoteHost =  gethostbyname(szhostname);
              if (remoteHost->h_addrtype == AF_INET) 
              {
                 if(remoteHost->h_addr_list[0] != 0) 
                 {
                  addr.s_addr = *(u_long *) remoteHost->h_addr_list[0];
                   return addr.s_addr;
                 }
             }


        }

        return 0;
    
    }
    bool   SelectSocket(SOCKET sock,NetType ntype);
private:
    SOCKET m_socketListen;
    SOCKET socketWaiter;

    HANDLE m_hThreadAccept;
    HANDLE m_hThreadRecv;
    bool   m_bflagQuit;

    HWND   m_hWnd;
    list<SOCKET> m_lstSocket;
     //1.定义数组
    fd_set fdsets;
    SOCKET socketEvent[10];
    int    m_nEventNum;
};

INet.cpp

#include "stdafx.h"
#include "INet.h"
CINet::CINet(void)
{
    m_socketListen = NULL;
    m_hThreadAccept = NULL;
    m_hThreadRecv = NULL;
    m_bflagQuit = false;
    m_nEventNum = 0;
      //2.初始化数组
     FD_ZERO(&fdsets);
}

CINet::~CINet(void)
{
}

bool CINet::InitNetWork(HWND hwnd)
{  //1.yi er  san  --  加载库

    if(NULL == hwnd)return false;
    m_hWnd = hwnd;
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;


    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
  
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) 
    {
        UnInitNetWork();
        return false;
    }
   
    //2.创建饭店-店长 --创建SOCKET 套接字
    m_socketListen =  socket(AF_INET,SOCK_STREAM,IPPROTO_TCP );
   if(INVALID_SOCKET  == m_socketListen)
   {
        UnInitNetWork();
        return false;
   }
    //3.穿上衣服(店地址,店名)--绑定 bind 
   sockaddr_in  addr;
   int n  = htons(1234);
   addr.sin_family = AF_INET;
   addr.sin_addr.s_addr = GetValidIp();
   addr.sin_port = htons(_DEFAULTPORT);
   if(SOCKET_ERROR ==  bind(m_socketListen,(const sockaddr*)&addr,sizeof(sockaddr_in)))
   {
        UnInitNetWork();
        return false;
   }
 
    //4.在一楼门口看着  --listen 
  if(SOCKET_ERROR  == listen(m_socketListen,MAXNUM))
  {
       UnInitNetWork();
        return false;
  }
  //5.接收客户端--线程
  m_bflagQuit = true;
  m_hThreadAccept = CreateThread(NULL,0,&ThreadAccept,this,0,0);
  m_hThreadRecv = CreateThread(NULL,0,&ThreadRecv,this,0,0);
    return true;
}
 
DWORD WINAPI CINet::ThreadAccept(void* lpvoid)   
{
    CINet *pthis = (CINet *)lpvoid;
    u_long argp = 1;
    while(pthis->m_bflagQuit)
    {
        pthis->socketWaiter = accept(pthis->m_socketListen,NULL,NULL);
        if(INVALID_SOCKET == pthis->socketWaiter)continue;

        char szbuf[100] = "客户端连接成功";
        PostMessage(pthis->m_hWnd,UM_DATA,(WPARAM)szbuf,0);
            //3.将sock 放入到数组中
         FD_SET(pthis->socketWaiter,&pthis->fdsets);
     
        //将socket属性改为非阻塞
       // ioctlsocket(pthis->socketWaiter,FIONBIO,&argp);
        pthis->m_lstSocket.push_back(pthis->socketWaiter);
    }
  
    return 0;
}

DWORD WINAPI CINet::ThreadRecv(void* lpvoid)   
{
    CINet *pthis = (CINet *)lpvoid;
    char szbuf[1024] = {0};
    int nRelNum = 0;
    list<SOCKET>::iterator ite;
    while(pthis->m_bflagQuit)
    {
         
         ZeroMemory(szbuf,1024);
       //  ite = pthis->m_lstSocket.begin();
      //   while(ite != pthis->m_lstSocket.end())
      //  {
             if(pthis->SelectSocket(0,NT_READ))
             {
                   
                  nRelNum = recv(pthis->socketEvent[--pthis->m_nEventNum],szbuf,1024,0);
                    if(nRelNum >0)
                    {
                        SendMessage(pthis->m_hWnd,UM_DATA,(WPARAM)szbuf,0);
                    }
                
                  
             }
            
            ite++;
       }
       
    }
  
    return 0;
}
 bool   CINet::SelectSocket(SOCKET sock,NetType ntype)
 {
     TIMEVAL tv;
     tv.tv_sec = 0;
     tv.tv_usec = 100;
     fd_set fdsetTEMP = fdsets;
    
     //2.初始化数组
    // FD_ZERO(&fdsets);
   //  FD_SET(socketWaiter,&fdsets);
     //4.将数组交给select 去管理
     if(ntype == NT_READ)
     {//按顺序逐个查看socket的状态
         select(NULL,&fdsets,NULL,NULL,&tv);
     }
     else  if(ntype == NT_WRITE)
     {
         select(NULL,NULL,&fdsets,NULL,&tv);
     }
     bool bflag = true;
   //   list<SOCKET>::iterator ite  = m_lstSocket.begin();
    //  int i = 0;
    //  m_nEventNum = 0;
    // while(ite != m_lstSocket.end())
   //  {
       if(0 != FD_ISSET(sock,&fdsets))
      {
           
          return false;
          
      }
           
    }
    
   
     return bflag;
 }

void CINet::UnInitNetWork()
{
     WSACleanup();
     if(m_socketListen)
     {
         closesocket(m_socketListen);
         m_socketListen = NULL;
     }


     m_bflagQuit = false;
     if(m_hThreadAccept)
     {
         if( WAIT_TIMEOUT ==  WaitForSingleObject(m_hThreadAccept,100))
         {
             TerminateThread(m_hThreadAccept,-1);
         }
         if( WAIT_TIMEOUT ==  WaitForSingleObject(m_hThreadRecv,100))
         {
             TerminateThread(m_hThreadRecv,-1);
         }
        CloseHandle(m_hThreadAccept);
        CloseHandle(m_hThreadRecv);
        m_hThreadAccept = NULL;
        m_hThreadRecv = NULL;

     }

}
    //3.发送数据
bool CINet::SendData(char *szContent,int nLen)
{
    return true;
}

2.异步选择模型(WSAAsyncSelect)

2.1异步选择模型介绍

  • WSAAsyncSelect模型是Select模型的异步版本,在调用select()函数时,会发生阻塞现象。可以通过select()函数timeout参数,设置函数调用的阻塞时间。在设定的时间内,线程保持等待,直到其中一个或多个套接字满足可读可写的条件时,该函数返回。
  • 该模型的核心即是WSAAsyncSelect函数,该函数是非阻塞的。
  • 要想使用 WSAAsyncSelect 模型,在应用程序中,必须有一个窗口,且该窗口有一个窗口例程函数(WinProc)。

与Select选择模型的异同

相同点:

他们都可以对Windows套接字应用程序所使用的多个套接字进行有效的管理。

不同点:
  • WSAAsyncSelect模型是异步的。在应用程序中调用WSAAsyncSelect()函数,通知系统感兴趣的网络事件,该函数立即返回,应用程序继续执行;
  • 发生网络事件时,应用程序得到的通知方式不同。Select()函数返回时,说明某个或者某些套接字满足可读可写的条件,应用程序需要使用FD_ISSET宏,判断套接字是否存在可读可写集合中。而对于WSAAsyncSelect模型来说,当网络事件发生时,系统向应用程序发送消息。
  • WSAAsyncSelect模型应用在基于消息的Windos环境下,使用该模型时必须创建窗口。而Select模型广泛应用在Unix系统和Windows系统,使用该模型不需要创建窗口。
  • 应用程序调用WSAAsyncSelect()函数后,自动将套接字设置为非阻塞模式。而应用程序中调用select()函数后,并不能改变套接字的工作方式

2.2WSAAsyncSelect 函数结构

int WSAAsyncSelect(
  		__in      SOCKET s,
  		__in      HWND hWnd,
  		__in      unsigned int wMsg,
  		__in      long lEvent
	);
  1.  s 参数指定的是我们感兴趣的那个套接字。
  2. hWnd 参数指定一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口。
  3. wMsg参数指定在发生网络事件时,打算接收的消息。该消息会投递到由hWnd窗口句柄指定的那个窗口。(通常,应用程序需要将这个消息设为比Windows的WM_USER大的一个值,避免网络窗口消息与系统预定义的标准窗口消息发生混淆与冲突)
  4. lEvent参数指定一个位掩码,对应于一系列网络事件的组合,大多数应用程序通常感兴趣的网络事件类型包括: FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE。当然,到底使用FD_ACCEPT,还是使用FD_CONNECT类型,要取决于应用程序的身份是客户端,还是服务器。如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位OR(或)运算,然后将它们分配给lEvent就可以了,例如:WSAAsyncSeltct(s,hwnd, WM_SOCKET, FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);

关于lEvent的说明:

  • FD_READ 应用程序想要接收有关是否可读的通知,以便读入数
  • FD_WRITE 应用程序想要接收有关是否可写的通知,以便写入数据
  • FD_ACCEPT 应用程序想接收与进入连接有关的通知
  • FD_CONNECT 应用程序想接收与一次连接完成的通知
  • FD_CLOSE 应用程序想接收与套接字关闭的通知

注意:

  1. 多个事件务必在套接字上一次注册!另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用closesocket命令,或者由应用程序针对那个套接字调用了WSAAsyncSelect,从而更改了注册的网络事件类型,否则的话,事件通知会永远有效!若将lEvent参数设为0,效果相当于停止在套接字上进行的所有网络事件通知。
  2. 若应用程序针对一个套接字调用了WSAAsyncSelect,那么套接字的模式会从“锁定”变成“非锁定”。这样一来,如果调用了像WSARecv这样的Winsock函数,但当时却并没有数据可用,那么必然会造成调用的失败,并返回WSAEWOULDBLOCK错误。为防止这一点,应用程序应依赖于由WSAAsyncSelect的uMsg参数指定的用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。
  3.  应用程序如何对 FD_WRITE 事件通知进行处理。
         只有在三种条件下,才会发出 FD_WRITE 通知:
  • 使用 connect 或 WSAConnect,一个套接字首次建立了连接。
  • 使用 accept 或 WSAAccept,套接字被接受以后。
  • 若 send、WSASend、sendto 或 WSASendTo 操作失败,返回WSAEWOULDBLOCK 错误,而且缓冲区的空间变得可用。

2.3WSAAsyncSelect窗口机制

应用程序在一个套接字上成功调用了WSAAsyncSelect之后,会在与hWnd窗口句柄对应的窗口例程中,以Windows消息的形式,接收网络事件通知。
窗口例程通常定义如下:
LRESULT CALLBACK WindowProc( 
	    HWND hwnd,
	    UINT uMsg,
	    WPARAM wParam,
	    LPARAM lParam
	);
● hWnd 参数指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。
● uMsg 参数指定需要对哪些消息进行处理。这里我们感兴趣的是WSAAsyncSelect调用中定义的消息。
● wParam 参数指定在其上面发生了一个网络事件的套接字。假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。
● lParam参数中,包含了两方面重要的信息。其中, lParam的低字(低位字)指定了已经发生的网络事件,而lParam的高字(高位字)包含了可能出现的任何错误代码。

步骤:

网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在网络错误。

这里有一个特殊的宏: WSAGETSELECTERROR,可用它返回高字位包含的错误信息。
若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,具体的做法便是读取lParam低字位的内容。

此时可使用另一个特殊的宏:WSAGETSELECTEVENT,用它返回lParam的低字部分。

2.4优势和不足

优势:
1.该模型的使用方便了在基于消息的Windows环境下开发套接字的应用程序。开发人员可以像处理其他消息一样对网络事件消息进行处理。
2.该模型确保接收所有数据提供了很好的机制。通过注册FD_CLOSE网络事件,从容关闭服务器与客户端的连接保证了数据全部接收。
不足:
1.该模型局限在,他基于Windows的消息机制,必须在应用程序中创建窗口。当然,在开发中可以根据具体情况是否显示该窗口。MFC的CSocketWnd类就是用来创建一个不显示的窗口,并在该类中声明接收网络事件消息处理函数。

2.由于调用WSAAsyncSelect()函数后,自动将套接字设置为非阻塞状态。当应用程序为接收到网络事件调用相应函数时,未必能够成功返回。这无疑增加了开发人员使用该模型的难度。对于这一点可以从MFC CSocket类的Accept()、Receive()和Send()函数的实现得到验证。

2.5代码实现


3.1事件选择IO介绍

WSAEventSelect模型是Windows Sockets提供的另外一个有用的异步I/O模型。该模型允许一个或多个套接字上接收以事件为基础的网络事件通知。Windows Sockets应用程序在创建套接字后,调用WSAEventSlect()函数,将一个事件对象与网络事件集合关联在一起。当网络事件发生时,应用程序以事件的形式接收网络事件通知。和 WSAAsyncSelect 模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知;最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递到一个窗口例程。从应用程序接收网络事件通知的方式来说,WSAEventSelect模型与WSAAsyncSelect模型都是被动的,当网络事件发生时,系统通知应用程序。然而select模型是主动的,应用程序主动调用该函数看是否发生了网络事件。

3.2WSAEventSelect函数介绍

int WSAEventSelect(
	  __in       SOCKET s,
	  __in        WSAEVENT hEventObject,
	  __in         long lNetworkEvents
	);

WSAEventSelect 函数的返回值很简单,就是一个创建好的事件对象句柄,接下来必须将其与某个套接字关联在一起,同时注册自己感兴趣的网络事件类型(FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE等)。

WSAEventSelect 函数的参数

● s 参数代表感兴趣的套接字。
● hEventObject 参数指定要与套接字关联在一起的事件对象—用WSACreateEvent 取得的那一个。

● lNetworkEvents 参数则对应一个“位掩码”,用于指定应用程序感兴趣的各种网络事件类型的一个组合。、

会用到的函数:

WSAEventSelect()
WSACreateEvent()
WSAResetEvent()

WSAWaitForMultipleEvents()

3.3WSAEventSelect优势和不足

优势:

可以在一个非窗口的Windows Sockets程序中,实现多个套接字的管理。

不足:
1.每个WSAEventSelect模型最多只能管理64个套接字。当应用程序中需要管理多于64个套接字时,就需要额外创建线程。

2.由于使用该模型开发套接字应用程序需要调用几个相关函数才能完成。因此,该模型增加了开发的难度,增加了开发人员的编码量。从这个角度讲,该模型不如WSAAysnceSelect模型方便。

3.4代码实现

WSAEventSelect.h

#pragma once
#include <map>
using namespace std;
#define _DEFAULTPORT  1234
#define MAXNUM        10 
#define UM_DATA    WM_USER + 1
#define MAXEVENTNUM    64
class CWSAEventSelect
{
public:
    CWSAEventSelect(void);
    ~CWSAEventSelect(void);
    public:
     //1.初始化网络
    bool InitNetWork(HWND hwnd);
    //2.卸载网络
    void UnInitNetWork();
    //3.发送数据
    bool SendData(char *szContent,int nLen);
    static unsigned _stdcall ThreadProc( void * lpvoid );
  
     //获得本机iP
    static long GetValidIp()
    {
        char szhostname[100] = {0};
        hostent *remoteHost = NULL;
        in_addr addr;
        //1.获得本机的名称
        if(!gethostname(szhostname,sizeof(szhostname)))
        {
              //2.通过名称获得IP地址
              remoteHost =  gethostbyname(szhostname);
              if (remoteHost->h_addrtype == AF_INET) 
              {
                 if(remoteHost->h_addr_list[0] != 0) 
                 {
                  addr.s_addr = *(u_long *) remoteHost->h_addr_list[0];
                   return addr.s_addr;
                 }
             }


        }

        return 0;
      
    }
private:
    HWND   m_hWnd;
    SOCKET m_socketListen;
  //  map<WSAEVENT,SOCKET> m_mapEventToSocket;
    WSAEVENT   m_aryEvent[MAXEVENTNUM];
    SOCKET     m_arySocket[MAXEVENTNUM];
    int        m_nEventNum;
};

WSAEventSelect.cpp

#include "stdafx.h"
#include "WSAEventSelect.h"
#include <process.h>
 
CWSAEventSelect::CWSAEventSelect(void)
{
    m_nEventNum = 0;
}

CWSAEventSelect::~CWSAEventSelect(void)
{
}

bool CWSAEventSelect::InitNetWork(HWND hwnd)
{  //1.yi er  san  --  加载库

    if(NULL == hwnd)return false;
    m_hWnd = hwnd;
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
  
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) 
    {
        UnInitNetWork();
        return false;
    }
   
    //2.创建饭店-店长 --创建SOCKET 套接字
    m_socketListen =  socket(AF_INET,SOCK_STREAM,IPPROTO_TCP );
   if(INVALID_SOCKET  == m_socketListen)
   {
        UnInitNetWork();
        return false;
   }
    //3.穿上衣服(店地址,店名)--绑定 bind 
   sockaddr_in  addr;
   
   addr.sin_family = AF_INET;
   addr.sin_addr.s_addr = GetValidIp();
   addr.sin_port = htons(_DEFAULTPORT);
   if(SOCKET_ERROR ==  bind(m_socketListen,(const sockaddr*)&addr,sizeof(sockaddr_in)))
   {
        UnInitNetWork();
        return false;
   }
 
    //4.在一楼门口看着  --listen 
  if(SOCKET_ERROR  == listen(m_socketListen,MAXNUM))
  {
       UnInitNetWork();
        return false;
  }

  //5.注册信息--socket 网络事件,事件
  WSAEVENT we = WSACreateEvent();
  if(!WSAEventSelect(m_socketListen,we,FD_ACCEPT))
  {
      m_aryEvent[m_nEventNum] = we;
      m_arySocket[m_nEventNum] = m_socketListen;
      m_nEventNum++;
  }
 
  _beginthreadex(NULL,0,&ThreadProc,this,0,NULL);
    return true;
}

unsigned _stdcall CWSAEventSelect::ThreadProc( void * lpvoid )
{
    CWSAEventSelect *pthis = (  CWSAEventSelect *)lpvoid;
    WSANETWORKEVENTS wwe;
    while(1)
    {
        int nindex = WSAWaitForMultipleEvents(pthis->m_nEventNum,pthis->m_aryEvent,FALSE,WSA_INFINITE,TRUE);
        if(WSA_WAIT_FAILED == nindex)
        {
            continue;
        }
        nindex -= WSA_WAIT_EVENT_0;

        //判断当前socket 发生什么网络事件
        if(!WSAEnumNetworkEvents(pthis->m_arySocket[nindex],pthis->m_aryEvent[nindex],&wwe))
        {
            if( wwe.lNetworkEvents & FD_ACCEPT)
            {
              SOCKET socketWaiter =  accept(pthis->m_arySocket[nindex],NULL,NULL);
              if(INVALID_SOCKET == socketWaiter)continue;
              WSAEVENT we = WSACreateEvent();
             if(!WSAEventSelect(socketWaiter,we,FD_READ|FD_WRITE|FD_CLOSE))
             {
                 pthis->m_aryEvent[pthis->m_nEventNum] = we;
                 pthis->m_arySocket[pthis->m_nEventNum] =socketWaiter;
                 pthis->m_nEventNum++;
             }
            }

            if(wwe.lNetworkEvents & FD_READ)
            {
                char szbuf[1024] = {0};
                int nres = recv(pthis->m_arySocket[nindex],szbuf,1024,0);
                if(nres > 0)
                {
                    SendMessage(pthis->m_hWnd,UM_DATA,(WPARAM)szbuf,0);
                }
            }
        }

    }
        
    return 0;
}

void CWSAEventSelect::UnInitNetWork()
{

}
    //3.发送数据
bool CWSAEventSelect::SendData(char *szContent,int nLen)
{
    return true;
}

4.重叠IO(Overlapped I/O)

在 Winsock 中,重叠 I/O(Overlapped I/O)模型能达到更佳的系统性能,高于之前讲过的三种。重叠模型的基本设计原理便是让应用程序使用一个重叠的数据结构(WSAOVERLAPPED),一次投递一个或多个 Winsock I/O 请求。针对这些提交的请求,在它们完成之后,我们的应用程序会收到通知,于是我们就可以对数据进行处理了。套接字的重叠I/O模型才是真正意义上的异步I/O模型。在应用程序中调用输入或输出函数后,立即返回。当I/O操作完成后,系统通知应用程序。利用该模型,应用程序在调用输入或者输出函数后,只需要等待I/O操作完成的通知即可。从发送和接收数据操作的角度看,前3个模型不是异步模型。因为在这3个模型中,I/O操作还是同步的。重叠I/O模型才是真正意义上的异步模型。

4.1重叠IO的实现

1.要想在一个套接字上使用重叠 I/O 模型,首先必须使用 WSA_FLAG_OVERLAPPED 这个标志,创建一个套接字。例如:

SOCKET s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
2.创建套接字的时候,假如使用的是 socket 函数,那么会默认设置 WSA_FLAG_OVERLAPPED 标志。
成功建好一个套接字,同时将其与一个本地接口绑定到一起后,便可开始进行重叠 I/O 操作,为了要使用重叠结构,我们常用的 send、recv 等收发数据的函数也都要被 WSASend、WSARecv 替换掉了,方法是调用下述的 Winsock 函数,同时指定一个 WSAOVERLAPPED 结构(可选),它们的用法我后面会讲到:
  • WSASend
  • WSASendTo
  • WSARecv
  • WSARecvFrom
  • WSAIoctl
  • AcceptEx
3.WSA_IO_PENDING : 最常见的返回值,这是说明我们的重叠函数调用成功了,但是 I/O 操作还没有完成。
4.若随一个 WSAOVERLAPPED 结构一起调用这些函数,函数会立即完成并返回,无论套接字是否设为阻塞模式。
那么我们如何来得知我们的 I/O 请求是否成功了呢?方法有两个:
  • 等待“事件对象通知”
  • 通过“完成例程”

4.2重叠IO的编程步骤

  1. 创建一个套接字,开始在指定的端口上监听连接请求。
  2. 接受一个客户端进入的连接请求。
  3. 为接受的套接字新建一个 WSAOVERLAPPED 结构,并为该结构分配一个事件对象句柄。同时将该事件对象句柄分配给一个事件数组,以便稍后由 WSAWaitForMultipleEvents 函数使用。
  4. 在套接字上投递一个异步 WSARecv 请求,指定参数为 WSAOVERLAPPED 结构。注意函数通常会以失败告终,返回 SOCKET_ERROR 错误状态 WSA_IO_PENDING(I/O操作尚未完成)。
  5. 使用步骤3)的事件数组,调用 WSAWaitForMultipleEvents 函数,并等待与重叠调用关联在一起的事件进入“已传信”状态(换言之,等待那个事件的“触发”)。
  6. WSAWaitForMultipleEvents 函数返回后,针对“已传信”状态的事件,调用 WSAResetEvent(重设事件)函数,从而重设事件对象,并对完成的重叠请求进行处理。
  7. 使用 WSAGetOverlappedResult 函数,判断重叠调用的返回状态是什么。
  8. 在套接字上投递另一个重叠 WSARecv 请求。
  9. 重复步骤5~8。

4.3代码实现:

WSASelect.h

#pragma once
#include "MyWnd.h"
#define _DEFAULTPORT  1234
#define MAXNUM        10 
#define UM_DATA    WM_USER + 1
#define UM_ACCEPT  WM_USER + 2
#define UM_TALKING  WM_USER + 3
class CWSASelect
{
public:
    CWSASelect(void);
    ~CWSASelect(void);
public:
     //1.初始化网络
    bool InitNetWork(HWND hwnd);
    //2.卸载网络
    void UnInitNetWork();
    //3.发送数据
    bool SendData(char *szContent,int nLen);
    void OnAccept(WPARAM wparam,LPARAM lparam);
    void OnTalking(WPARAM wparam,LPARAM lparam);
     //获得本机iP
    static long GetValidIp()
    {
        char szhostname[100] = {0};
        hostent *remoteHost = NULL;
        in_addr addr;
        //1.获得本机的名称
        if(!gethostname(szhostname,sizeof(szhostname)))
        {
              //2.通过名称获得IP地址
              remoteHost =  gethostbyname(szhostname);
              if (remoteHost->h_addrtype == AF_INET) 
              {
                 if(remoteHost->h_addr_list[0] != 0) 
                 {
                  addr.s_addr = *(u_long *) remoteHost->h_addr_list[0];
                   return addr.s_addr;
                 }
             }


        }

        return 0;

    }
private:
    HWND   m_hWnd;
    SOCKET m_socketListen;
   // CMyWnd m_pWnd;
};

WSASelect.cpp

#include "stdafx.h"
#include "wsaselect.h"

CWSASelect::CWSASelect(void)
{
    m_socketListen = NULL;
}

CWSASelect::~CWSASelect(void)
{
}

bool CWSASelect::InitNetWork(HWND hwnd)
{  //1.yi er  san  --  加载库

    if(NULL == hwnd)return false;
    m_hWnd = hwnd;
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;


    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
  
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) 
    {
        UnInitNetWork();
        return false;
    }
 
    //2.创建饭店-店长 --创建SOCKET 套接字
    m_socketListen =  socket(AF_INET,SOCK_STREAM,IPPROTO_TCP );
   if(INVALID_SOCKET  == m_socketListen)
   {
        UnInitNetWork();
        return false;
   }
    //3.穿上衣服(店地址,店名)--绑定 bind 
   sockaddr_in  addr;
   
   addr.sin_family = AF_INET;
   addr.sin_addr.s_addr = GetValidIp();
   addr.sin_port = htons(_DEFAULTPORT);
   if(SOCKET_ERROR ==  bind(m_socketListen,(const sockaddr*)&addr,sizeof(sockaddr_in)))
   {
        UnInitNetWork();
        return false;
   }
 
    //4.在一楼门口看着  --listen 
  if(SOCKET_ERROR  == listen(m_socketListen,MAXNUM))
  {
       UnInitNetWork();
        return false;
  }
  //创建窗口--处理消息
  if(!CMyWnd::CreateMyObject()->Create(NULL,"MyWnd"))
  {
       UnInitNetWork();
        return false;
  }
   CMyWnd::CreateMyObject()->SetServer(this);

  //5.注册信息--socket 网络事件,消息
  WSAAsyncSelect(m_socketListen,CMyWnd::CreateMyObject()->m_hWnd,UM_ACCEPT,FD_ACCEPT);
  
    return true;
}
 
 void CWSASelect::OnAccept(WPARAM wparam,LPARAM lparam)
 {
      //接收连接 
      SOCKET SocketWaiter =  accept(m_socketListen,NULL,NULL);
      //再次向windows注册
      WSAAsyncSelect(SocketWaiter,CMyWnd::CreateMyObject()->m_hWnd,UM_TALKING,FD_CLOSE|FD_READ |FD_WRITE);
 }

 void CWSASelect::OnTalking(WPARAM wparam,LPARAM lparam)
 {
     SOCKET sock = ( SOCKET )wparam;
        //判断当前发生什么网络事件--lparam
      //谁发生了网络是事件--wparam
     char szbuf[1024] = {0};
     switch (lparam)
     {
     case FD_READ:
         {
            int nres = recv(sock,szbuf,1024,0);
            if(nres > 0)
            {
                SendMessage(m_hWnd,UM_DATA,(WPARAM)szbuf,0);
            }
         }
         break;
     case FD_WRITE: //1.accept //2.connect //3.send发送失败
         TRACE("FD_WRITE\n");
         break;
     case FD_CLOSE:
            TRACE("FD_CLOSE\n");
         break;
     default:
         break;
     }

    //  recv(,)
 }
 
void CWSASelect::UnInitNetWork()
{
     WSACleanup();
     if(m_socketListen)
     {
         closesocket(m_socketListen);
         m_socketListen = NULL;
     }

}
    //3.发送数据
bool CWSASelect::SendData(char *szContent,int nLen)
{
    return true;
}

3事件选择(WSAEventSelect)


5.完成端口

5.1完成端口介绍

   完成端口是Win32一种核心对象。利用完成端口模型,套接字应用程序能够管理数百个甚至上千个套接字。应用程序创建一个Win32完成端口对象,通过指定一定数量的服务线程,为已经完成的重叠I/O操作提供服务。该模型往往可以达到最好的系统性能。完成端口是真正意义上的异步模型。该模型解决了“one-thread-per-client”的问题。当应用程序需要管理成百上千个套接字,并且希望随着系统安装的CPU数量的增加,应用程序的性能得到提升时,I/O完成端口模型是最好的选择。完成端口目标是实现高效的服务器程序,他克服了并发模型的不足。其方法一是为完成端口指定并发线程的数量;二是在初始化套接字时创建一定数量的服务线程,即所谓的线程池。当客户端请求到来时,这些线程立即为之服务。完成端口的理论基础是并行运行的线程数量必须有一个上限。这个数值就是CPU的个数。如果一台机器有两个CPU,那么多于两个可运行的线程就没有意义了。因为一旦运行线程数目超出CPU数目,系统就不得花费时间来进行线程上下文的切换,这将浪费宝贵的CPU周期。完成端口并行运行的线程数目和应用程序创建的线程数量是两个不同的概念。服务器应用程序需要创建多少个服务器线程,一般规律是CPU数目乘以2.例如,单CPU的机器,套接字应用程序应该创建2个线程的线程池。接下来的问题是,完成端口如何实现对线程池的有效管理,使这些服务线程高效运行起来。当系统完成I/O操作后,向服务器完成端口发送I/O completion packet。这个过程发生在系统内部,对应用程序是不可见的。在应用程序方面,此时线程池中的线程在完成端口上排队等待I/O操作完成。如果在完成端口上没有接收到I/O completion packet时,这些线程处于睡吧状态。当I/O completion packet 被送到完成端口时,这些线程按照后进先出(LIFO Last-in-First-out)方式被唤醒。完成端口之所以采用这种方式,其目的是为了提高性能。例如,有3个线程在完成端口上等待,当一个I/O completion packet到达后,队中最后一个线程被唤醒。该线程为客户端完成服务后,继续在完成端口上等待。如果 此时又有一个I/O completion packet 到达完成端口,则该线程线程又被唤醒,为该客户端提供服务。如果完成端口不采用LIFO方式,完成端口唤醒另外一个线程,则必然要进行线程之间的上下文切换。通过使用LIFO方式,还可以使得不被唤醒的线程内存资源从缓存中清除。在前面讲到的,应用程序需要创建一个线程池,在完成端口上等待。线程池中的线程数目一定大于完成端口并发运行的线程数目,似乎应用程序创建了多余的线程,其实不然,之所以这样做是因为保证CPU尽可能的忙碌。例如,在一台单CPU的计算机上,创建一个完成端口的应用程序,为其制定并发线程数目为1.在应用程序中,创建2个线程在完成端口上等待。假如在一次为客户端服务时,被唤醒的线程因调用Sleep()之类的函数而处于阻塞状态,此时,另外一个I/O completion packet 被发送到完成端口上。完成端口会唤醒另外一个线程为该客户提供服务。这就是线程池中线程数目要大于完成端口指定的并发线程数量的原因。根据上面分析,在某些情况下,完成端口并行运行的线程数量会超过指定数量。但是,当服务线程为客户端完成服务后,在完成端口等待时,并发的线程数量还会下降。总之,完成端口为套接字应用程序管理线程池,避免反复创建线程的开销,同时,根据CPU的数量决定并发线程的数量,减少线程的调度,从而提高服务器程序性能。

5.2与重叠IO的比较

重叠I/O模型和完成端口模型相同点在于他们都是异步模型,都可以使得套接字应用程序性能得到改善。重叠I/O模型与完成端口模型相比存在以下不足:在事件通知方式的套接字应用程序中,使用WSAWaitForMultipleEvents()函数,应用程序最多等待WSA_MAXIMUM_WAIT_EVENTS个事件对象。在Win32 SDK 中,该值为64.作为一个服务器程序,该函数限制了服务器为之提供服务的客户端的数量。应用程序必须维护一个“事件—套接字—重叠结构”关系表格。根据发生的事件对象,确定套接字和重叠结构。一个套接字可以关联一个、两个或多个事件对象,而事件对象与重叠结构之间保持着一一对应的关系。应用程序管理这个关系表格时,如果出现一点疏漏,就会造成严重的后果。

完成端口优点

完成端口实际上一个通知队列。当某项I/O操作完成时,由操作系统向完成端口发送通知包。一方面,这些通知包在完成端口上排队,按照FIFO(First_in_First_out)方式被提取。另一方面,在完成端口上一定数量的现场等待接收通知包,这些线程按照LIFO方式被唤醒。套接字在被创建后,可以在任何时候与某个完成端口进行关联。对发起重叠操作的数量不存在限制。支持scalable架构。Scalable系统是指随着RAM、磁盘空间或者CPU个数的增加而能够提升应用程序效能的一种系统。

5.3CreateCompletionPort函数

使用这种模型之前,首先要创建一个 I/O 完成端口对象,用它面向任意数量的套接字句柄,管理多个 I/O 请求。要做到这一点,需要调用 CreateCompletionPort 函数,其定义如下:

HANDLE WINAPI CreateIoCompletionPort(
  __in          HANDLE FileHandle,
  __in          HANDLE ExistingCompletionPort,
  __in          ULONG_PTR CompletionKey,
  __in          DWORD NumberOfConcurrentThreads
);
要注意该函数有两个功能:
● 用于创建一个完成端口对象;
● 将一个句柄同完成端口对象关联到一起。
参数问题:
如果仅仅为了创建一个完成端口对象,唯一注意的参数便是 NumberOfConcurrentThreads(并发线程的数量),前面三个参数可忽略。NumberOfConcurrentThreads 参数的特殊之处在于,它定义了在一个完成端口上,同时允许执行的线程数量。

理想情况下,我们希望每个处理器各自负责一个线程的运行,为完成端口提供服务,避免过于频繁的线程“场景”(即线程上下文)切换。若将该参数设为0,表明系统内安装了多少个处理器,便允许同时运行多少个工作者线程!可用下述代码创建一个 I/O 完成端口:HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)。成功创建一个完成端口后,便可开始将套接字句柄与其关联到一起。但在关联套接字之前,首先必须创建一个或多个“工作者线程”,以便在 I/O 请求投递给完成端口后,为完成端口提供服务。在这个时候,大家或许会觉得奇怪,到底应创建多少个线程,以便为完成端口提供服务呢?在此,要记住的一点,我们调用 CreateIoComletionPort 时指定的并发线程数量,与打算创建的工作者线程数量相比,它们代表的不是同一件事情。CreateIoCompletionPort 函数的 NumberOfConcurrentThreads 参数明确指示系统:在一个完成端口上,一次只允许 n 个工作者线程运行。假如在完成端口上创建的工作者线程数量超出 n 个,那么在同一时刻,最多只允许n个线程运行。但实际上,在一段较短的时间内,系统有可能超过这个值,但很快便会把它减少至事先在 CreateIoCompletionPort 函数中设定的值。那么,为何实际创建的工作者线程数量有时要比 CreateIoCompletionPort 函数设定的多一些呢?这样做有必要吗?这主要取决于应用程序的总体设计情况。假定我们的某个工作者线程调用了一个函数,比如 Sleep 或 WaitForSingleObject,进入了暂停(锁定或挂起)状态,那么允许另一个线程代替它的位置。换言之,我们希望随时都能执行尽可能多的线程;当然,最大的线程数量是事先在 CreateIoCompletonPort 调用里设定好的。这样一来,假如事先预计到自己的线程有可能暂时处于停顿状态,那么最好能够创建比CreateIoCompletonPort 的 NumberOfConcurrentThreads 参数的值多的线程,以便到时候充分发挥系统的潜力。

5.4完成端口步骤

  • 创建一个完成端口,第四个参数保持为 0,指定在完成端口上,每个处理器一次只允许执行一个工作者线程。
  • 判断系统内到底安装了多少个处理器。
  • 根据处理器的数量创建工作者线程。
  • 准备好一个监听套接字,在端口 9527 上监听进入的连接请求。
  • 使用 accept 函数,接受进入的连接请求。
  • 创建一个数据结构,用于容纳“单句柄数据”,同时在结构中存入接受的套接字句柄。
  • 调用 CreateIoCompletionPort 函数,将从 accept 返回的新套接字句柄同完成端口关联到一起。
  • 开始在已接受的连接上进行 I/O 操作。
  • 重复步骤 5 ~ 8,直至服务器中止。

5.5完成端口代码

IOServer.h

#pragma once
#include <list>

using namespace std;

#define _DEFAULTPORT  1234
#define MAXNUM        10 
#define UM_DATA    WM_USER + 1


enum NetType{NT_UNKOWN,NT_ACCEPT,NT_READ,NT_WRITE};
struct Node
{
   Node()
   {
       m_olp.hEvent = NULL;
       m_socket =  NULL;
       m_ntype =  NT_UNKOWN;
       ZeroMemory(m_szbuf,sizeof(m_szbuf));
   }
   OVERLAPPED m_olp; //事件 --通知
   SOCKET     m_socket; //发送网络事件的socket
   NetType    m_ntype; //发送的网络事件
   char       m_szbuf[1024]; //接收数据的缓冲区

};
class CIOServer
{
public:
    CIOServer(void);
    ~CIOServer(void);
public:
     //1.初始化网络
    bool InitNetWork(HWND hwnd);
    //2.卸载网络
    void UnInitNetWork();
    //3.发送数据
    bool SendData(char *szContent,int nLen);
    //4.投递接收连接的请求
    bool PostAccept();
    //5.投递接收数据的请求
    bool PostRecv(Node *pNode);
    //线程
   static unsigned  __stdcall ThreadProc( void* lpvoid );

        //获得本机iP
    static long GetValidIp()
    {
        char szhostname[100] = {0};
        hostent *remoteHost = NULL;
        in_addr addr;
        //1.获得本机的名称
        if(!gethostname(szhostname,sizeof(szhostname)))
        {
              //2.通过名称获得IP地址
              remoteHost =  gethostbyname(szhostname);
              if (remoteHost->h_addrtype == AF_INET) 
              {
                 if(remoteHost->h_addr_list[0] != 0) 
                 {
                  addr.s_addr = *(u_long *) remoteHost->h_addr_list[0];
                   return addr.s_addr;
                 }
             }


        }

        return 0;
      
    }
private:
    HWND   m_hWnd;
    SOCKET m_socketListen;
    HANDLE m_hIoCom;
    list<HANDLE>  m_lstHandle;
    list<Node*>   m_lstNode;
    bool    m_bflagQuit;
};

IOServer.cpp

#include "stdafx.h"
#include "IOServer.h"
#include <process.h>
#include < Mswsock.h >
CIOServer::CIOServer(void)
{ 
    m_bflagQuit  = false;
}


CIOServer::~CIOServer(void)
{
}

bool CIOServer::InitNetWork(HWND hwnd)
{  //1.yi er  san  --  加载库

    if(NULL == hwnd)return false;
    m_hWnd = hwnd;
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;


    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
  
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) 
    {
        UnInitNetWork();
        return false;
    }
   
        

   

    //2.创建饭店-店长 --创建SOCKET 套接字
    m_socketListen =  WSASocket (AF_INET,SOCK_STREAM,IPPROTO_TCP,0,0,WSA_FLAG_OVERLAPPED);
   if(INVALID_SOCKET  == m_socketListen)
   {
        UnInitNetWork();
        return false;
   }
    //3.穿上衣服(店地址,店名)--绑定 bind 
   sockaddr_in  addr;
   
   addr.sin_family = AF_INET;
   addr.sin_addr.s_addr = GetValidIp();
   addr.sin_port = htons(_DEFAULTPORT);
   if(SOCKET_ERROR ==  bind(m_socketListen,(const sockaddr*)&addr,sizeof(sockaddr_in)))
   {
        UnInitNetWork();
        return false;
   }
 
    //4.在一楼门口看着  --listen 
  if(SOCKET_ERROR  == listen(m_socketListen,MAXNUM))
  {
       UnInitNetWork();
        return false;
  }

  //5.主动投递接收连接请求
    for(int i =0; i <MAXNUM;i++)
    {
        if(!PostAccept())
        {
            continue;
        }
    }

    //6.创建完成端口
    m_hIoCom = CreateIoCompletionPort( INVALID_HANDLE_VALUE,NULL,NULL,NULL);
    if( NULL == m_hIoCom)
    {
       UnInitNetWork();
        return false;
    }
    //6.1将socket交给完成端口管理
    CreateIoCompletionPort((HANDLE)m_socketListen,m_hIoCom,m_socketListen,0);



    //7.创建线程池
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    m_bflagQuit =true;
    for(UINT i = 0;i < si.dwNumberOfProcessors*2;i++)
    {
        HANDLE hthread = (HANDLE) _beginthreadex(NULL,0,&ThreadProc,this,0,NULL);
        if(hthread)
        {
            m_lstHandle.push_back(hthread);
        }
    }


    return true;
}

unsigned  __stdcall CIOServer::ThreadProc( void* lpvoid )
{
    CIOServer *pthis = ( CIOServer *)lpvoid;
    BOOL  bflag;
    DWORD dwNumberOfBytes;
    SOCKET sock;
    Node *pNode = NULL;
    while(pthis->m_bflagQuit)
    {
        //观察完成端口的状态
        bflag = GetQueuedCompletionStatus(pthis->m_hIoCom,&dwNumberOfBytes,(PULONG_PTR)&sock,(LPOVERLAPPED*)&pNode,WSA_INFINITE);
        if(!bflag)
          continue;

        //处理数据
        if(pNode && sock)
        {
            switch (pNode->m_ntype)
            {
            case NT_ACCEPT:
                {
                    //将waiter交给完成端口管理
                    CreateIoCompletionPort((HANDLE)pNode->m_socket,pthis->m_hIoCom,pNode->m_socket,0);

                    //投递接收数据的请求
                    pthis->PostRecv(pNode);
                    //接收连接的请求
                    pthis->PostAccept();

                }
                break;
            case NT_READ:
                {
                    SendMessage(pthis->m_hWnd,UM_DATA,(WPARAM)pNode->m_szbuf,0);
                       //投递接收数据的请求
                    pthis->PostRecv(pNode);
                }
                break;
            case NT_WRITE:
                break;
            default:
                break;
            }
        }

    }
    return 0;
}

bool CIOServer::PostRecv(Node *pNode)
{
    WSABUF wb;
    DWORD dwNumberOfBytesRecvd;
    DWORD dwFlags  = false;
    wb.buf = pNode->m_szbuf;
    wb.len = sizeof(pNode->m_szbuf);
    pNode->m_ntype = NT_READ;

    if(WSARecv(pNode->m_socket,&wb,1,&dwNumberOfBytesRecvd,&dwFlags,&pNode->m_olp,NULL))
    {
        if(WSAGetLastError() !=  WSA_IO_PENDING)
        {
            return false;
        }
    }
    return true;
}

bool CIOServer::PostAccept()
{
    SOCKET socketWaiter =  WSASocket (AF_INET,SOCK_STREAM,IPPROTO_TCP,0,0,WSA_FLAG_OVERLAPPED);
    DWORD dwBytesReceived;
    Node *pNewNode = new Node;
    pNewNode->m_socket = socketWaiter;
    pNewNode->m_olp.hEvent = WSACreateEvent();
    pNewNode->m_ntype = NT_ACCEPT;

   if(!AcceptEx(m_socketListen,socketWaiter,pNewNode->m_szbuf,0,
        sizeof(sockaddr_in)+ 16, sizeof(sockaddr_in)+ 16,&dwBytesReceived,&pNewNode->m_olp))
   {
       if(WSAGetLastError() != ERROR_IO_PENDING)
       {
           return false;
       }
   }
    m_lstNode.push_back(pNewNode);
    return true;
}

void CIOServer::UnInitNetWork()
{
    m_bflagQuit = false;

    //向完成端口发退出通知
    int nNum = m_lstHandle.size();
    while(nNum-- >0)
    {
        PostQueuedCompletionStatus(m_hIoCom,NULL,NULL,NULL);
    }

    Sleep(500);

    list<HANDLE>::iterator  ite = m_lstHandle.begin();

    while(ite != m_lstHandle.end())
    {
        if(WAIT_TIMEOUT == WaitForSingleObject((*ite),100))
        {
            TerminateThread(*ite,-1);
        }
        CloseHandle(*ite);
        *ite = NULL;

        ite++;
    }

    list<Node*>::iterator  iteNode = m_lstNode.begin();
     while(iteNode != m_lstNode.end())
    {
        if(*iteNode)
        {
            delete *iteNode;
            *iteNode = NULL;
        }
        iteNode++;
         
    }

     WSACleanup();
     //关闭socket
     if(m_socketListen)
     {
         closesocket(m_socketListen);
         m_socketListen= NULL;
     }

}

    //3.发送数据
bool CIOServer::SendData(char *szContent,int nLen)
{
    return true;
}


  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值