• Windows下性能最好的I/O模型——完成端口


    I/O模型——完成端口


    设计目的:

      常见的网络通信分为两种:同步和异步。
      在同步通信中,每一次接受数据都会导致主线程的挂起,从而阻塞住了其他操作。为了解决这一问题,我们通常会采取同步通信+多线程的策略,即为每一个连入的Socket分配一个线程。然而随着连入的Socket的数量的增加,线程的数量也在增加,这样CPU则需要不停地进行线程的切换,因此难以成为高性能的服务器程序。
      异步通信则可以把接收数据这一操作交给内核,即在内核接收数据的时候,主线程可以不用被阻塞并且继续执行其他操作,而一旦接收数据完成以后,再由内核通知主线程。而如何通知主线程是一个关键,不同的异步通信策略有着不同的通知方式。
      在这样的情况下,完成端口这一I/O模型被提出,成为目前Windows下性能最好的I/O模型之一。
      

    实现原理:

      首先根据CPU数量开好线程,当有用户请求的时候,把这些请求加入一个特定的消息队列中,而事先开好的线程则会排队从这个消息队列中获取请求并作出处理。完成端口正是指这一消息队列.
      

    基本流程:


    主要的API:

    • 创建完成端口
    HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 ); 
    
    HANDLE WINAPI CreateIoCompletionPort(
      _in_      HANDLE  FileHandle,  
      // Socket的句柄,置为INVALID_HANDLE_VALUE表示创建一个没有和任何HANDLE有关系的完成端口
      
      _in_opt   HANDLE  ExistingCompletionPort,  
      // NULL表示新建一个完成端口
      
      _in_      ULONG_PTR CompletionKey, 
      // 完成键,创建完成端口时置为0 
      
      _in_      DWORD NumberOfConcurrentThreads 
      // 完成端口并发线程的数量,置0表示有多少个CPU就开多少个线程
    );
    
    • 创建监听Socket
    初始化Socket库...
    ...
    listenSoc = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    ...
    绑定端口,并监听...
    
    • 将监听的Socket绑定到完成端口上,这里同样使用HANDLE WINAPI CreateIoCompletionPort(...)这一API.
    CreateIoCompletionPort(listenSoc, iocp, CompKey, 0);
    
    HANDLE WINAPI CreateIoCompletionPort(
      _in_      HANDLE  FileHandle,  
      // 监听Socket的句柄
      
      _in_opt   HANDLE  ExistingCompletionPort,  
      // 刚才创建的完成端口
      
      _in_      ULONG_PTR CompletionKey, 
      // 完成键,我们在绑定的同时为其分配一段内存空间,以存储与这一Socket相关的信息,当网络操作完成的时候,我们可以根据这段内存空间里面的信息分辨这是哪一个Socket 
      
      _in_      DWORD NumberOfConcurrentThreads 
      // 完成端口并发线程的数量,置0表示有多少个CPU就开多少个线程
    );
    
    • 在监听端口上投递AcceptEX请求
      AcceptEX与传统的Accept有三个主要不同点:
    1. AcceptEX采取异步方式,可以同时投递多个请求,而Accept采取阻塞的方式,依次只能处理一个请求。
    2. AcceptEX会事先准备好Socket,当用户请求连入的时候直接使用这一新的Socket,避免临时创建Socket。
    3. AcceptEX接受连入请求的同时,我们可以附加一些数据,这样我们就可以在接受用户连入的同时,接受来自用户的第一组数据。
    BOOL AcceptEx ( 	
      SOCKET sListenSocket,  // 监听Socket
      SOCKET sAcceptSocket,  // 事先准备好给新用户的Socket
      PVOID lpOutputBuffer,  // 接受缓冲区
      DWORD dwReceiveDataLength,  // 用于存放用户第一组数据的空间大小
      DWORD dwLocalAddressLength, // 本地地址的空间大小
      DWORD dwRemoteAddressLength, // 客户端地址的空间大小
      LPDWORD lpdwBytesReceived, 
      LPOVERLAPPED lpOverlapped  
      // 重叠结构,每一个网络操作都会对应一个重叠结构,相当于网络操作的ID
    );
    
    • 投递接受数据请求
    int WSARecv(
      SOCKET s,  // 接受数据的Socket
      LPWSABUF lpBuffers,  // 接收缓冲区 
      DWORD dwBufferCount,  // 置为1
      LPDWORD lpNumberOfBytesRecvd,  // 所接收到的字节数
      LPDWORD lpFlags,  // 置为0
      LPWSAOVERLAPPED lpOverlapped,  // 这个Socket对应的重叠结构
      NULL
    );
    
    • 解析AcceptEX接收到的数据
      AcceptEX缓冲区里面保存着本地地址,客户端地址以及客户端发来的第一组数据,因此我们需要使用GetAcceptExSockAddrs()来解析这些数据.
    void GetAcceptExSockaddrs(
      _In_   PVOID lpOutputBuffer,
      // AcceptEX中的缓冲区
      _In_   DWORD dwReceiveDataLength,
      // 用户第一组数据的空间大小
      _In_   DWORD dwLocalAddressLength,
      // 本地地址的空间大小
      _In_   DWORD dwRemoteAddressLength,
      // 客户端地址的空间大小
      _Out_  LPSOCKADDR *LocalSockaddr,
      // 本地地址
      _Out_  LPINT LocalSockaddrLength,
      // 实际本地地址的空间大小
      _Out_  LPSOCKADDR *RemoteSockaddr,
      // 客户端地址
      _Out_  LPINT RemoteSockaddrLength
      // 实际客户端地址的大小
    );
    

    参考: 1. 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
        2. Overlapped模型深入分析

  • 相关阅读:
    Android JS 交互出现 Uncaught Error: Error calling method on NPObject
    adapter.notifydatasetchanged()没有效果
    Android 正则表达式验证手机号码
    Android SpannableString实现TextView的点击事件
    使用Jquery的Ajax调用
    我们常用,却容易忽视——CSS的BFC(Block formatting contexts)
    React数据流和组件间的通信总结
    CSS清除浮动float方法总结
    CSS3幻灯片制作心得
    JavaScript中map函数和filter的简单举例
  • 原文地址:https://www.cnblogs.com/bgmind/p/3984947.html
Copyright © 2020-2023  润新知