精华内容
下载资源
问答
  • Windows网络异步IO模型

    2019-06-03 18:10:00
    一分钱一分货,简单的搭建,必然带来简陋的性能,或者说,可以通过一些多线程、事件通知等工具,亲手打造一个更优的服务器,但毕竟这些工作,已经有现成的模型,何必呢。 现成的模型分为以下几类: SELECT ...

    话不多说,直接进入主题。

    编写一个TCP\UDP网络服务器,自然就想到最简单的api,通过socket、bind、listen、accept、send、recv可以搭建一个阻塞的服务器。

    一分钱一分货,简单的搭建,必然带来简陋的性能,或者说,可以通过一些多线程、事件通知等工具,亲手打造一个更优的服务器,但毕竟这些工作,已经有现成的模型,何必呢。

    现成的模型分为以下几类:

    1. SELECT
    2. WSAAsynsSelect,基于windows窗体的异步IO
    3. WSAEventSelect,基于windows事件的异步IO
    4. 重叠IO

    其中重叠IO,也就是Overlapped IO,又分为下面三种:

    1. 基于事件的重叠IO
    2. 完成例程
    3. 完成端口

    以上提到的模型,复杂程度递增,当然,性能也是递增的。

    下面一个个的讲出重点,本文是为了记录每个网络IO模型的关键点,省略了细节。必须提一句,所提到的模型,可以应用在客户端、服务端。

    一、Select模型

    关键在于FD_SET这个结构体,结构体如下所示,说白了,就是一个列表,保存着我们感兴趣的套接字,

    typedef struct fd_set {
            u_int fd_count;               /* how many are SET? */
            SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
    } fd_set;

    同样关键的,当然是select这个api了,函数如下:

    select(
        _In_ int nfds,//为了兼容伯克利api,方便程序移植,无用
        _Inout_opt_ fd_set FAR * readfds,//需要关注“读”的套接字
        _Inout_opt_ fd_set FAR * writefds,//需要关注“写”的套接字
        _Inout_opt_ fd_set FAR * exceptfds,//需要关注“异常”的套接字
        _In_opt_ const struct timeval FAR * timeout//超时设置结构体
        );

    剩下就是一些宏定义,方便我们设置FD_SET,此处不展开,

    FD_SET、FD_ZERO、FD_ISSET、FD_CLR

    select模型思想的关键,其实所有的网络IO模型,都可以理解成事件触发,至于是通过何种形式触发,是通过回调函数、事件通知,whatever,无非是把我们关注的套接字,套到一个loop里面去,通过操作系统内核,告诉我们读和写的时机。

    20190628 补充一个select demo

    #include <WinSock2.h>  
    #include <Windows.h>  
    #include <MSWSock.h>  
    #include <stdio.h>  
    #include <map>  
    using namespace std;
    
    
    #pragma comment(lib,"Ws2_32.lib")  
    #pragma comment(lib,"Mswsock.lib")  
    
    
    int main()
    {
    
        WSAData wsaData;
        if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
        {
            printf("初始化失败!%d\n", WSAGetLastError());
            Sleep(5000);
            return -1;
        }
    
        USHORT nport = 9995;
        SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
        u_long ul = 1;
        ioctlsocket(sListen, FIONBIO, &ul);
    
        sockaddr_in sin;
        sin.sin_family = AF_INET;
        sin.sin_port = htons(nport);
        sin.sin_addr.S_un.S_addr = ADDR_ANY;
    
    
        if (SOCKET_ERROR == bind(sListen, (sockaddr*)&sin, sizeof(sin)))
        {
            printf("bind failed!%d\n", WSAGetLastError());
            Sleep(5000);
            return -1;
        }
    
    
        listen(sListen, 5);
    
    
        //1)初始化一个套接字集合fdSocket,并将监听套接字放入  
        fd_set socketSet;
        FD_ZERO(&socketSet);
        FD_SET(sListen, &socketSet);
    
        TIMEVAL time = { 1,0 };
        char buf[4096];
    
    
        fd_set    readSet;
        FD_ZERO(&readSet);
    
        fd_set    writeSet;
        FD_ZERO(&writeSet);
    
    
        while (true)
        {
            //2)将fdSocket的一个拷贝fdRead传给select函数  
            readSet = socketSet;
            writeSet = socketSet;
    
            //同时检查套接字的可读可写性。
            int   nRetAll = select(0, &readSet, &writeSet, NULL, NULL/*&time*/);//若不设置超时则select为阻塞  
            if (nRetAll >0)   //-1
            {
                //是否存在客户端的连接请求。  
                if (FD_ISSET(sListen, &readSet))//在readset中会返回已经调用过listen的套接字。  
                {
    
                    if (socketSet.fd_count < FD_SETSIZE)
                    {
                        sockaddr_in addrRemote;
                        int nAddrLen = sizeof(addrRemote);
                        SOCKET sClient = accept(sListen, (sockaddr*)&addrRemote, &nAddrLen);
                        if (sClient != INVALID_SOCKET)
                        {
                            FD_SET(sClient, &socketSet);//新的客户端socket添加到socketSet中
                            printf("\n接收到连接:(%s)", inet_ntoa(addrRemote.sin_addr));
                        }
                    }
                    else
                    {
                        printf("连接数量已达上限!\n");
                        continue;
                    }
                }
    
            //此处有个需要注意的地方,如果是刚添加到socketSet的客户端socket,下面检测FD_ISSET是无效的,原因是此时该客户端套接字还未经过select
            //所以得经历下次select循环才会触发读写
    for (int i = 0; i<socketSet.fd_count; i++) { if (FD_ISSET(socketSet.fd_array[i], &readSet)) { //调用recv,接收数据。 int nRecv = recv(socketSet.fd_array[i], buf, 4096, 0); if (nRecv > 0) { buf[nRecv] = 0; printf("\nrecv %d : %s", socketSet.fd_array[i], buf); } } if (FD_ISSET(socketSet.fd_array[i], &writeSet)) { //调用send,发送数据。 char buf[] = "hello!"; int nRet = send(socketSet.fd_array[i], buf, strlen(buf) + 1, 0); if (nRet <= 0) { if (GetLastError() == WSAEWOULDBLOCK) { //do nothing } else { closesocket(socketSet.fd_array[i]); FD_CLR(socketSet.fd_array[i], &socketSet); } } else { printf("\nsend hello!"); } } } } else if (nRetAll == 0) { printf("time out!\n"); } else { printf("select error!%d\n", WSAGetLastError()); Sleep(5000); break; } Sleep(1000); } closesocket(sListen); WSACleanup(); }

     

    二、WSAAsynsSelect


    基于win窗体的异步IO模型,顾名思义,我们需要有一个窗体,还有一个关键api如下

    WSAAsyncSelect(
        _In_ SOCKET s,
        _In_ HWND hWnd,
        _In_ u_int wMsg,
        _In_ long lEvent
        );

    第一个参数当然是我们感兴趣的套接字,

    第二个参数就是我们需要创建的窗体句柄,

    第三个参数,是我们定义的WM_USER消息,用于在窗体响应函数中辨别消息

    第四个就是我们感兴趣的套接字事件了,使用逻辑或,FD_ACCEPT|FD_CLOSE|FD_RECV,可以监听我们感兴趣的所有事件。

     

    三、WSAEventSelect

     

    *完成端口

    完成端口实在看了很多次了,或许该承认自己没天才,需要更努力。

    今天开始有些理解。

    IOCP的关键点,我认为是在于两个自定义的结构体,一个是CreateIoCompletionPort接口需要传入的CompletionKey,另一个则是投递AcceptEx、WSARecv、WSASend等接口时,需要传入的OVERLAPPED结构体

    CreateIoCompletionPort(
        _In_ HANDLE FileHandle,
        _In_opt_ HANDLE ExistingCompletionPort,
        _In_ ULONG_PTR CompletionKey,
        _In_ DWORD NumberOfConcurrentThreads
        );
    
    WSARecv(
    _In_ SOCKET s,
    _In_reads_(dwBufferCount) __out_data_source(NETWORK) LPWSABUF lpBuffers,
    _In_ DWORD dwBufferCount,
    _Out_opt_ LPDWORD lpNumberOfBytesRecvd,
    _Inout_ LPDWORD lpFlags,
    _Inout_opt_ LPWSAOVERLAPPED lpOverlapped,
    _In_opt_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
    );
    
    WSASend(
        _In_ SOCKET s,
        _In_reads_(dwBufferCount) LPWSABUF lpBuffers,
        _In_ DWORD dwBufferCount,
        _Out_opt_ LPDWORD lpNumberOfBytesSent,
        _In_ DWORD dwFlags,
        _Inout_opt_ LPWSAOVERLAPPED lpOverlapped,
        _In_opt_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
        );

    下面详细说说这两个自定义结构体的内容。

    CompletionKey:一个套接字的上下文。在IOCP中,每个套接字,都需要与完成端口进行绑定,在绑定的时候,需要指定这个Key,实际上,这个Key,就是这个套接字的上下文,上下文存放何种数据,由我们自己定义,例如我们可以存放客户端的ip地址,客户端连入服务器的时间等等。我们在绑定IOCP的时候,传入什么内容的Key,在工作线程中,就会返回什么Key。可以说这个Key,是我们丢给操作系统,Key在系统黑盒子游走了一圈,在工作线程中,又原封不动的返回给了我们。个人感觉这个Key作用不大,如果难以一时间理解,甚至可以先不管。

    OverLapped结构体:一个套接字对应每次IO的上下文(下面简称OL)。重点在“每次IO”,如果把Key想象成连接到我们构建服务器的一个客户,那么OL结构体就记录着这个客户每次对服务器的操作。

    typedef struct _OVERLAPPED {
        ULONG_PTR Internal;
        ULONG_PTR InternalHigh;
        union {
            struct {
                DWORD Offset;
                DWORD OffsetHigh;
            } DUMMYSTRUCTNAME;
            PVOID Pointer;
        } DUMMYUNIONNAME;
    
        HANDLE  hEvent;
    } OVERLAPPED, *LPOVERLAPPED;

    OL结构体如下所示,在windows中已经定义了结构体了,所以上文提及的“自定义结构体”说法,似乎有些矛盾。其实不然,我们可以自定义一个结构体,只要把OL结构体置于我们定义的结构体首个位置,那么就可以通过宏CONTAINING_RECORD,来提取完整的自定义结构体数据内容,毕竟通过内存地址,就可以找到相应的数据了,这点不难理解。

    说了这么多,或许有些抽象,直接上伪代码

    //主线程

    CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 ); //建立完成端口 简单的传参,不多余解释 listenSocket = WSASocket(...) //通过WSASocket建立监听的套接字 PER_SOCKET_CONTEXT *listen_socket_context = new PER_SOCKET_CONTEXT(); CreateIoCompletionPort(listen_socket_context) //构建Key,将监听套接字绑定到完成端口 for(..) { prepare_socket = WSASocket(...) PER_IO_CONTEXT *per_io_context=new PER_IO_CONTEXT(); per_io_context->socket = prepare_socket AcceptEx(listenSocket,prepare_socket,per_io_context) //提前构建N个socket,还有对应的OL结构体,通过AcceptEx投递到完成端口中 } //主线程工作到此结束,其余交给工作线程 StartThread(WorkerThread);//启动工作线程,又名搬砖线程。想不到写个程序都能体会到打工阶级被剥削的命运。

    下面是工作线程,想到工作线程这么辛苦,就想到同样是搬砖命运的自己

    GetQueuedCompletionStatus(&per_socket_context,&ol);
    //通过宏,提取整个PER_IO_CONTEXT,方便方便
    PER_IO_CONTEXT* pIoContext = CONTAINING_RECORD(ol);  
    
    switch(pIoContext->operation)
    {
        case ACCEPT:
            GetAcceptExSockAddrs(ol);//提取accpet后,首次读取的数据
            //如果是ACCEPT操作,传参per_socket_context是监听套接字对应的Key,此处没有任何作用的
            PER_SOCKET_CONTEXT *client_socket_context = new PER_SOCKET_CONTEXT
            client_socket_context->socket = ol->socket;//将OL结构体的套接字赋予新建的Key
            CreateIoCompletionPort(client_socket_context)//新的客户端Key,绑定到完成端口上
            
            PER_IO_CONTEXT *client_io_context = new PER_IO_CONTEXT;//新的客户端,创建一个OL结构体,传参的ol不能用!!!等下需要完整无缺重新投递一个AcceptEx
            WSARecv(client_socket_context->socket,client_io_context)//客户端套接字,投递一个“读”
            
            ol->socket = WSASocket();//重新新建一个socket,准备下一个AcceptEx投递,原来的套接字,已经提供给client_socket_context使用了。
            AcceptEx(per_socket_context->socket,ol->socket)//不厌其烦说一下,第一个参数是监听的套接字,第二个是上一行新建的套接字,提供下一次使用
        break;
    
        case RECV:
            Print(ol->buf );//读取的数据,很自然很高效就出现在这里了,不用再WSARecv
            WSARecv(ol->socket,ol);//再次投递,很简单
        break;
    }

    重点部分已经结构,而后就是些优雅退出的过程了,这里先不赘述了。

     

    转载于:https://www.cnblogs.com/shawnc24/p/10967660.html

    展开全文
  • Windows异步IO模型详解

    千次阅读 2018-03-27 17:38:15
    1.选择模型1.1选择模型介绍选择(select)模型是Winsock中最常见的 I/O模型。核心便是利用 select 函数,实现对 I/O的管理。利用 select 函数来判断某Socket上是否有数据可读,或者能否向一个套接字写入数据,防止...

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


    展开全文
  • Windows Socket常见异步IO模型——代码攻略  如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。Windows操作系统提供了选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect...

    Windows Socket常见异步IO模型——代码攻略

      如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。Windows操作系统提供了选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)和完成端口(Completion Port)共五种I/O模型。每一种模型均适用于一种特定的应用场景。程序员应该对自己的应用需求非常明确,而且综合考虑到程序的扩展性和可移植性等因素,作出自己的选择。 

            我会以一个回应反射式服务器(与《Windows网络编程》第八章一样)来介绍这五种I/O模型。
            我们假设客户端的代码如下(为代码直观,省去所有错误检查,以下同):

            #include <WINSOCK2.H>
            #include <stdio.h>

            #define SERVER_ADDRESS "137.117.2.148"
            #define PORT           5150
            #define MSGSIZE        1024

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

            int main()
            {
                    WSADATA     wsaData;
                    SOCKET      sClient;
                    SOCKADDR_IN server;
                    char        szMessage[MSGSIZE];
                    int         ret;
      
                    // Initialize Windows socket library
                    WSAStartup(0x0202, &wsaData);

                    // Create client socket
                    sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

                    // Connect to server
                    memset(&server, 0, sizeof(SOCKADDR_IN));
                    server.sin_family = AF_INET;
                    server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);
                    server.sin_port = htons(PORT);

                    connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));

                    while (TRUE)
                    {
                            printf("Send:");
                            gets(szMessage);

                            // Send message
                            send(sClient, szMessage, strlen(szMessage), 0);

                            // Receive message
                            ret = recv(sClient, szMessage, MSGSIZE, 0);
                            szMessage[ret] = '\0';

                            printf("Received [%d bytes]: '%s'\n", ret, szMessage);
                    }

                    // Clean up
                    closesocket(sClient);
                    WSACleanup();
                    return 0;
            }

            客户端所做的事情相当简单,创建套接字,连接服务器,然后不停的发送和接收数据。

            比较容易想到的一种服务器模型就是采用一个主线程,负责监听客户端的连接请求,当接收到某个客户端的连接请求后,创建一个专门用于和该客户端通信的套接字和一个辅助线程。以后该客户端和服务器的交互都在这个辅助线程内完成。这种方法比较直观,程序非常简单而且可移植性好,但是不能利用平台相关的特性。例如,如果连接数增多的时候(成千上万的连接),那么线程数成倍增长,操作系统忙于频繁的线程间切换,而且大部分线程在其生命周期内都是处于非活动状态的,这大大浪费了系统的资源。所以,如果你已经知道你的代码只会运行在Windows平台上,建议采用Winsock I/O模型。

            一.选择模型
            Select(选择)模型是Winsock中最常见的I/O模型。之所以称其为“Select模型”,是由于它的“中心思想”便是利用select函数,实现对I/O的管理。最初设计该模型时,主要面向的是某些使用UNIX操作系统的计算机,它们采用的是Berkeley套接字方案。Select模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。由于Winsock 1.1向后兼容于Berkeley套接字实施方案,所以假如有一个Berkeley套接字应用使用了select函数,那么从理论角度讲,毋需对其进行任何修改,便可正常运行。(节选自《Windows网络编程》第八章)
    下面的这段程序就是利用选择模型实现的Echo服务器的代码(已经不能再精简了):

            #include <winsock.h>
            #include <stdio.h>

            #define PORT       5150
            #define MSGSIZE    1024

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

            int    g_iTotalConn = 0;
            SOCKET g_CliSocketArr[FD_SETSIZE];

            DWORD WINAPI WorkerThread(LPVOID lpParameter);

            int main()
            {
                    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);
                    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
                            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 WINAPI WorkerThread(LPVOID lpParam)
            {
                    int            i;
                    fd_set         fdread;
                    int            ret;
                    struct timeval tv = {1, 0};
                    char           szMessage[MSGSIZE];
      
                    while (TRUE)
                    {
                            FD_ZERO(&fdread);
                            for (i = 0; i < g_iTotalConn; i++)
                            {
                                    FD_SET(g_CliSocketArr[i], &fdread);
                            }

                            // We only care read event
                            ret = select(0, &fdread, NULL, NULL, &tv);

                            if (ret == 0)
                            {
                                    // Time expired
                                    continue;
                            }

                            for (i = 0; i < g_iTotalConn; i++)
                            {
                                    if (FD_ISSET(g_CliSocketArr[i], &fdread))
                                    {
                                            // A read event happened on g_CliSocketArr[i]
                                            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);
                                            }
                                    }
                            }
                    }
      
                    return 0;
            }

            服务器的几个主要动作如下:
            1.创建监听套接字,绑定,监听;
            2.创建工作者线程;
            3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组;
            4.接受客户端的连接。这里有一点需要注意的,就是我没有重新定义FD_SETSIZE宏,所以服务器最多支持的并发连接数为64。而且,这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。一种比较好的实现方案就是采用WSAAccept函数,而且让WSAAccept回调自己实现的Condition Function。如下所示:

            int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
            {
                    if (当前连接数 < FD_SETSIZE)
                            return CF_ACCEPT;
                    else
                            return CF_REJECT;
            }

            工作者线程里面是一个死循环,一次循环完成的动作是:
            1.将当前所有的客户端套接字加入到读集fdread中;
            2.调用select函数;
            3.查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上)

            除了需要有条件接受客户端的连接外,还需要在连接数为0的情形下做特殊处理,因为如果读集中没有任何套接字,select函数会立刻返回,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100%。

            二.异步选择
            Winsock提供了一个有用的异步I/O模型。利用这个模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用WSAAsyncSelect函数。该模型最早出现于Winsock的1.1版本中,用于帮助应用程序开发者面向一些早期的16位Windows平台(如Windows for Workgroups),适应其“落后”的多任务消息环境。应用程序仍可从这种模型中得到好处,特别是它们用一个标准的Windows例程(常称为"WndProc"),对窗口消息进行管理的时候。该模型亦得到了Microsoft Foundation Class(微软基本类,MFC)对象CSocket的采纳。(节选自《Windows网络编程》第八章)
            我还是先贴出代码,然后做详细解释:
            #include <winsock.h>
            #include <tchar.h>

            #define PORT      5150
            #define MSGSIZE   1024
            #define WM_SOCKET WM_USER+0

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

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

            int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
            {
                    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, iCmdShow);
                    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)
            {
                    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
                                    listen(sListen, 3);

                                    // Associate listening socket with FD_ACCEPT event
                                    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;
                                    }
        
                                    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
                                                    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';
                                                            send(wParam, szMessage, strlen(szMessage), 0);
                                                    }
                                                    break;
          
                                            case FD_CLOSE:
                                                    closesocket(wParam);      
                                                    break;
                                    }
                                    return 0;
                    }
      
                    return DefWindowProc(hwnd, message, wParam, lParam);
            }


            在我看来,WSAAsyncSelect是最简单的一种Winsock I/O模型(之所以说它简单是因为一个主线程就搞定了)。使用Raw Windows API写过窗口类应用程序的人应该都能看得懂。这里,我们需要做的仅仅是:
            1.在WM_CREATE消息处理函数中,初始化Windows Socket library,创建监听套接字,绑定,监听,并且调用WSAAsyncSelect函数表示我们关心在监听套接字上发生的FD_ACCEPT事件;
            2.自定义一个消息WM_SOCKET,一旦在我们所关心的套接字(监听套接字和客户端套接字)上发生了某个事件,系统就会调用WndProc并且message参数被设置为WM_SOCKET;
            3.在WM_SOCKET的消息处理函数中,分别对FD_ACCEPT、FD_READ和FD_CLOSE事件进行处理;
            4.在窗口销毁消息(WM_DESTROY)的处理函数中,我们关闭监听套接字,清除Windows Socket library

            下面这张用于WSAAsyncSelect函数的网络事件类型表可以让你对各个网络事件有更清楚的认识:
            表1

            FD_READ         应用程序想要接收有关是否可读的通知,以便读入数据 
            FD_WRITE         应用程序想要接收有关是否可写的通知,以便写入数据 
            FD_OOB         应用程序想接收是否有带外(OOB)数据抵达的通知 
            FD_ACCEPT         应用程序想接收与进入连接有关的通知 
            FD_CONNECT         应用程序想接收与一次连接或者多点join操作完成的通知 
            FD_CLOSE         应用程序想接收与套接字关闭有关的通知 
            FD_QOS         应用程序想接收套接字“服务质量”(QoS)发生更改的通知 
            FD_GROUP_QOS          应用程序想接收套接字组“服务质量”发生更改的通知(现在没什么用处,为未来套接字组的使用保留) 
            FD_ROUTING_INTERFACE_CHANGE         应用程序想接收在指定的方向上,与路由接口发生变化的通知 
            FD_ADDRESS_LIST_CHANGE          应用程序想接收针对套接字的协议家族,本地地址列表发生变化的通知

            三.事件选择
            Winsock提供了另一个有用的异步I/O模型。和WSAAsyncSelect模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。对于表1总结的、由WSAAsyncSelect模型采用的网络事件来说,它们均可原封不动地移植到新模型。在用新模型开发的应用程序中,也能接收和处理所有那些事件。该模型最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。(节选自《Windows网络编程》第八章)
            还是让我们先看代码然后进行分析:
            #include <winsock2.h>
            #include <stdio.h>

            #define PORT    5150
            #define MSGSIZE 1024

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

            int      g_iTotalConn = 0;
            SOCKET   g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
            WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];

            DWORD WINAPI WorkerThread(LPVOID);
            void Cleanup(int index);

            int main()
            {
                    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));

                    // Listen
                    listen(sListen, 3);

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

                    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 socket with network event
                            g_CliSocketArr[g_iTotalConn] = sClient;
                            g_CliEventArr[g_iTotalConn] = WSACreateEvent();
                            WSAEventSelect(g_CliSocketArr[g_iTotalConn], g_CliEventArr[g_iTotalConn], FD_READ | FD_CLOSE);
                            g_iTotalConn++;
                    }
            }

            DWORD WINAPI WorkerThread(LPVOID lpParam)
            {
                    int              ret, index;
                    WSANETWORKEVENTS NetworkEvents;
                    char             szMessage[MSGSIZE];

                    while (TRUE)
                    {
                            ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
                            if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
                            {
                                    continue;
                            }

                            index = ret - WSA_WAIT_EVENT_0;
                            WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &NetworkEvents);

                            if (NetworkEvents.lNetworkEvents & FD_READ)
                            {
                                    // Receive message from client
                                    ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);
                                    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
                                    {
                                            Cleanup(index);
                                    }
                                    else
                                    {
                                            szMessage[ret] = '\0';
                                            send(g_CliSocketArr[index], szMessage, strlen(szMessage), 0);
                                    }
                            }

                            if (NetworkEvents.lNetworkEvents & FD_CLOSE)
                            {
                                    Cleanup(index);
                            }
                    }
                    return 0;
            }

            void Cleanup(int index)
            {
                    closesocket(g_CliSocketArr[index]);
                    WSACloseEvent(g_CliEventArr[index]);

                    if (index < g_iTotalConn - 1)
                    {
                            g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
                            g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
                    }
      
                    g_iTotalConn--;
            }


            事件选择模型也比较简单,实现起来也不是太复杂,它的基本思想是将每个套接字都和一个WSAEVENT对象对应起来,并且在关联的时候指定需要关注的哪些网络事件。一旦在某个套接字上发生了我们关注的事件(FD_READ和FD_CLOSE),与之相关联的WSAEVENT对象被Signaled。程序定义了两个全局数组,一个套接字数组,一个WSAEVENT对象数组,其大小都是MAXIMUM_WAIT_OBJECTS(64),两个数组中的元素一一对应。
            同样的,这里的程序没有考虑两个问题,一是不能无条件的调用accept,因为我们支持的并发连接数有限。解决方法是将套接字按MAXIMUM_WAIT_OBJECTS分组,每MAXIMUM_WAIT_OBJECTS个套接字一组,每一组分配一个工作者线程;或者采用WSAAccept代替accept,并回调自己定义的Condition Function。第二个问题是没有对连接数为0的情形做特殊处理,程序在连接数为0的时候CPU占用率为100%。

    展开全文
  • WinSock异步IO模型

    2019-02-26 14:29:51
    如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。 Windows操作系统提供了五种I/O模型,分别是: ■ 选择(select); ■ 异步选择(WSAAsyncSelect); ■ 事件选择(WSAEventSelect); ■ ...

    如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。
    Windows操作系统提供了五种I/O模型,分别是:

    ■ 选择(select);
    ■ 异步选择(WSAAsyncSelect);
    ■ 事件选择(WSAEventSelect);
    ■ 重叠I/O(Overlapped I/O);
    ■ 完成端口(Completion Port) 。

    展开全文
  • 异步选择(WSAAsyncSelect)模型:属于被动通知,基于消息机制,需要向Windows注册消息,当对应的网络事件发生时,向指定窗口发送指定消息。 缺点:(1)消息不安全,而且会慢,消息的过程比较复杂。(2)基于窗口,必须...
  • 当前Windows支持的各种Socket I/O模型,主要的模型如下: 一:select模型 二:WSAAsyncSelect模型 三:WSAEventSelect模型 四:Overlapped I/O 事件通知模型 五:Overlapped I/O 完成例程模型 六:IOCP模型...
  • 事件选择(WSAEventSelect)模型:基于事件通知,事件是一个有操作系统管理的内核对象,当前系统基于事件机制,该模型则可以使用,事件两种状态,有信号和无信号,检测事件,若有信号则进行相应处理。 参考博客:...
  • 如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。 Windows操作系统提供了五种I/O模型,分别是: ■ 选择(select); ■ 异步选择(WSAAsyncSelect); ■ 事件选择(WSAEventSelect); ■ ...
  • (关于异步io的理解详情可以看:http://www.cnblogs.com/curo0119/p/8461520.html) 它可以用来在一个socket上接收以windows消息为基础的网络事件。它提供了读写数据的异步通知功能,但不提供异步数据传送。WSAA...
  • windows网络IO模型:IOCP

    2019-04-28 17:53:24
    完成端口IOCP是一个异步IO模型, IO设计模式 属于Proactor。 2. 它开启多个工作者线程,每个工作者线程负责多个socket的处理。 3. 它利用操作系统的内核程序来并发处理多个IO,避免某个线程被单个耗时的I...
  • WINDOWS重叠IO模型

    千次阅读 2015-07-30 21:03:15
    重叠IO模型简介 重叠IO的核心实际上就是一个重叠的数据结构。应用程序在单个套接字上投递一个或者多个IO操作,当IO操作完成时对应的重叠数据结构中的事件对象会受信,相应的应用程序通过查事件对象可以得到通知...
  • 说到异步IO网络服务器,全国人民都知道:“Windows下用iocp,Linux下用epoll”。无数的高手写过文章来讲IOCP和epoll相关的文章。本人分别使用epoll和iocp开发过网络库,也使用过boost.asio,试归纳如下:  epoll:...
  • windows的重叠IO模型

    2019-05-29 14:03:00
    windows的重叠IO模型 windows的重叠IO模型 2019年5月29日 11:58 同一线程内部向多个目标传输(或冲多个目标接收)数据引起的IO重叠现象称为"重叠IO"。为了完成这项任务,调用IO的函数应立即...
  • 异步选择(WSAAsyncSelect)模型是一个有用的异步 I/O 模型。利用这个模型,应用程序可在一个套接字上,接收以 Windows 消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用WSAAsyncSelect函数。该模
  • windows网络socket模型 异步通信模型 select模型 异步选择 异步事件 重叠IO 完成端口 详细介绍了区别 用法与实例
  • WSAEventSelect 是 WinSock 提供的一种异步事件通知I/O模型,与 WSAAsyncSelect模型有些类似。该模型同样是接收 FD_XXX 之类的网络事件,但是是通过事件对象句柄通知,而非像 WSAAsyncSelect一样依靠Windows的消息...
  • Windows和Linux IO模型简单介绍Socket IO模型Windows下的Socket IOLinux下的Socket IO两个操作系统下IO模型的区别差异 Socket IO模型 IO模型相信大家都很熟悉,主要就分下面几种: 同步 异步 阻塞 非阻塞 同步和...
  • 一、线程创建函数CreateThread() ... 头文件:#include&...windows.h&amp;amp;amp;gt; 原型: HANDLE WINAPI CreateThread() 参数6个: 默认值 代表含义 安全性 NULL 线程安全,不被继承 栈空间 ...

空空如也

空空如也

1 2 3 4 5 ... 15
收藏数 281
精华内容 112
关键字:

windows异步io模型