• 完成端口


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

    事件内核对象《基于事件通知的重叠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.一旦连接成功,投递接收数据请求

  • 相关阅读:
    webpack源码学习总结
    并发容器(三)非阻塞队列的并发容器
    并发容器(二)阻塞队列详细介绍
    并发容器(一)同步容器 与 并发容器
    java内存模型(二)深入理解java内存模型的系列好文
    java内存模型(一)正确使用 Volatile 变量
    原子操作类(二)原子操作的实现原理
    原子操作类(一)原子操作类详细介绍
    同步锁源码分析(一)AbstractQueuedSynchronizer原理
    并发工具类(五) Phaser类
  • 原文地址:https://www.cnblogs.com/Lune-Qiu/p/8629462.html
Copyright © 2020-2023  润新知