完成端口

通知应用程序处理网络数据的几种方法:

事件内核对象《基于事件通知的重叠I/O模型》:缺点:WaitForMultipleObjects()64个Event等待上限的限制

《基于完成例程的重叠I/O模型》:就是发出请求的线程必须得要自己去处理接收请求,负载均衡问题

完成端口(内核对象):网络操作完成的通知,都放在这个队列里面,开好的线程排队从这个队列里面取就行了,取走一个就少一个…。

线程池(多个线程通信accept)+共享内存(将接受区投递出去,内核和用户映射同一块接受区域,解决阻塞问题):

WSAAsyncSelect或者是WSAEventSelect这两个异步模型,没有用到Overlapped机制,虽然实现了异步的接收,但是却不能进行异步的发送。

函数介绍

一 . 创建完成端口:HANDLE m_hIOCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 ); 

HANDLE WINAPI CreateIoCompletionPort( 

    __in      HANDLE  FileHandle,             // 这里当然是连入的这个套接字句柄了

     __in_opt  HANDLE  ExistingCompletionPort, // 这个就是前面创建的那个完成端口

     __in      ULONG_PTR CompletionKey,        //这个参数就是类似于线程参数一样,在绑定的时候把自己定义的结构体指针传递这样到了Worker线程中
                             //也可以使用这个 结构体的数据了,相当于参数的传递
__in DWORD NumberOfConcurrentThreads // 这里同样置0 );

但是对于最后一个参数 0,我这里要简单的说两句,这个0可不是一个普通的0,它代表的是NumberOfConcurrentThreads,也就是说,允许应用程序同时执行的线程数量。当然,我们这里为了避免上下文切换,最理想的状态就是每个处理器上只运行一个线程了,所以我们设置为0,就是说有多少个处理器,就允许同时多少个线程运行。

二 . AcceptEx

AcceptEx比Accept又强大在哪里呢?

1.AcceptEx是在客户端连入之前,就把客户端的Socket建立好了。

2.AcceptEx可以同时在完成端口上投递多个请求。

3.顺便在AcceptEx的同时,收取客户端发来的第一组数据

 获取AcceptEx函数指针的代码大致如下:

   LPFN_ACCEPTEX     m_lpfnAcceptEx;         // AcceptEx函数指针

GUID GuidAcceptEx = WSAID_ACCEPTEX; // GUID,这个是识别AcceptEx函数必须的   DWORD dwBytes = 0;  WSAIoctl( m_pListenContext->m_Socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidAcceptEx,         sizeof(GuidAcceptEx), &m_lpfnAcceptEx, sizeof(m_lpfnAcceptEx), &dwBytes, NULL, NULL);
Accept函数的参数:
BOOL AcceptEx(
  _In_   SOCKET sListenSocket, //
  _In_   SOCKET sAcceptSocket, //事先建好的
  _In_   PVOID lpOutputBuffer, //接收缓冲区,一是客户端发来的第一组数据,二是server的地址,三是client地址
  _In_   DWORD dwReceiveDataLength,//前面那个参数lpOutputBuffer中用于存放数据的空间大小
                     //如果此参数=0,则Accept时将不会待数据到来,而直接返回,如果此参数不为0,
                     //那么一定得等接收到数据了才会返回                                     
//需要Accept接收数据时,就需要将该参数设成为:
                     //sizeof(lpOutputBuffer) - 2*(sizeof sockaddr_in +16)
 _In_ DWORD dwLocalAddressLength, //存放本地址地址信息的空间大小                     _In_ DWORD dwRemoteAddressLength,//存放本远端地址信息的空间大小;  _Out_ LPDWORD lpdwBytesReceived, _In_ LPOVERLAPPED lpOverlapped //重叠结构 );
异步操作,我们在线程启动的地方投递这个操作, 等我们再次见到这些变量的时候,就已经是在Worker线程内部了,
因为Windows会直接把操作完成的结果传递到Worker线程里

这样咱们在启动的时候投递了那么多的IO请求,这从Worker线程传回来的这些结果,到底是对应着哪个IO请求的呢?
  

    这里的标志就是如下这样的结构体:

typedef struct _PER_IO_CONTEXT{ 

  OVERLAPPED   m_Overlapped;          // 每一个重叠I/O网络操作都要有一个             

   SOCKET       m_sockAccept;          // 这个I/O操作所使用的Socket,每个连接的都是一样的

   WSABUF       m_wsaBuf;              // 存储数据的缓冲区,用来给重叠操作传递参数的,关于WSABUF后面还会讲

   char         m_szBuffer[MAX_BUFFER_LEN]; // 对应WSABUF里的缓冲区

   OPERATION_TYPE  m_OpType;               // 标志这个重叠I/O操作是做什么的,例如Accept/Recv等

} PER_IO_CONTEXT, *PPER_IO_CONTEXT; 

一个Socket要投递很多次:

ypedef struct _PER_SOCKET_CONTEXT 

{   

  SOCKET                   m_Socket;              // 每一个客户端连接的Socket

  SOCKADDR_IN              m_ClientAddr;          // 这个客户端的地址

  CArray<_PER_IO_CONTEXT*>  m_arrayIoContext;   // 数组,所有客户端IO操作的参数,

// 也就是说对于每一个客户端Socket

// 是可以在上面同时投递多个IO请求的

} PER_SOCKET_CONTEXT, *PPER_SOCKET_CONTEXT; 

 

三 .监控完成端口

BOOL WINAPI GetQueuedCompletionStatus( 

    __in   HANDLE          CompletionPort,    // 这个就是我们建立的那个唯一的完成端口

    __out  LPDWORD         lpNumberOfBytes,   // 操作完成后返回的字节数

    __out  PULONG_PTR      lpCompletionKey,   // 这个是我们建立完成端口的时候绑定的那个自定义结构体参数

    __out  LPOVERLAPPED    *lpOverlapped,     // 这个是我们在连入Socket的时候一起建立的那个重叠结构

    __in   DWORD           dwMilliseconds     // 等待完成端口的超时时间,如果线程不需要做其他的事情,那就INFINITE就行了

    ); 

四.获取客户端的连入地址信息GetAcceptExSockAddrs()

确保我们在结构体PER_IO_CONTEXT定义的时候,把Overlapped变量,定义为结构体中的第一个成员。

PER_IO_CONTEXT* pIoContext = CONTAINING_RECORD(lpOverlapped, PER_IO_CONTEXT, m_Overlapped); 

         这个宏的含义,就是去传入的lpOverlapped变量里,找到和结构体中PER_IO_CONTEXT中m_Overlapped成员相关的数据。

运行过程:

 

1.加载库    2.socket   3.bind   4.listen

5.创建几个waiter 

6.创建完成端口(listen) 

 

7.将ListenSocket发给完成端口

8.线程池

9.看完成端口状态

10.一旦连接成功,投递接收数据请求

 

posted @ 2018-03-23 11:47  Lune-Qiu  阅读(291)  评论(1编辑  收藏  举报