zoukankan      html  css  js  c++  java
  • [转载]IOCP模型的总结

    IOCP(I/O Completion Port,I/O完成端口)是性能最好的一种I/O模型。它是应用程序使用线程池处理异步I/O请求的一种机制。在处理多个并发的异步I/O请求时,以往的模型都是在接收请求是创建一个线程来应答请求。这样就有很多的线程并行地运行在系统中。而这些线程都是可运行的,Windows内核花费大量的时间在进行线程的上下文切换,并没有多少时间花在线程运行上。再加上创建新线程的开销比较大,所以造成了效率的低下。

    而IOCP模型是事先开好了N个线程,存储在线程池中,让他们hold。然后将所有用户的请求都投递到一个完成端口上,然后N个工作线程逐一地从完成端口中取得用户消息并加以处理。这样就避免了为每个用户开一个线程。既减少了线程资源,又提高了线程的利用率。

    完成端口模型是怎样实现的呢?我们先创建一个完成端口(
    ::CreateIoCompletioPort())。然后再创建一个或多个工作线程,并指定他们到这个完成端口上去读取数据。我们再将远程连接的套接字句柄关联到这个完成端口(还是用::CreateIoCompletionPort())。一切就OK了。

    工作线程都干些什么呢?首先是调用
    ::GetQueuedCompletionStatus()函数在关联到这个完成端口上的所有套接字上等待I/O的完成。再判断完成了什么类型的I/O。一般来说,有三种类型的I/O,OP_ACCEPT,OP_READ和OP_WIRTE。我们到数据缓冲区内读取数据后,再投递一个或是多个同类型的I/O即可(::AcceptEx()::WSARecv()::WSASend())。对读取到的数据,我们可以按照自己的需要来进行相应的处理。

    为此,我们需要一个以OVERLAPPED(重叠I
    /O)结构为第一个字段的per-I/O数据自定义结构。

    typedef struct _PER_IO_DATA
    {
            
    OVERLAPPED ol;      
    // 重叠I/O结构
            
    char buf[BUFFER_SIZE];  // 数据缓冲区
            
    int nOperationType;         //I/O操作类型
    #define OP_READ 1
    #define OP_WRITE 2
    #define OP_ACCEPT 3
    } PER_IO_DATA, *PPER_IO_DATA;

    将一个PER_IO_DATA结构强制转化成一个OVERLAPPED结构传给::GetQueuedCompletionStatus()函数,返回的这个PER_IO_DATA结构的的nOperationType就是I/O操作的类型。当然,这些类型都是在投递I/O请求时自己设置的。

    这样一个IOCP服务器的框架就出来了。当然,要做一个好的IOCP服务器,还有考虑很多问题,如内存资源管理、接受连接的方法、恶意的客户连接、包的重排序等等。以上是个人对于IOCP模型的一些理解与看法,还有待完善。另外各Winsock API的用法参见MSDN。
     

    补充IOCP模型的实现:

    //创建一个完成端口
    FCompletPort := CreateIoCompletionPort( INVALID_HANDLE_VALUE, 0,0,0 );

    //接受远程连接,并把这个连接的socket句柄绑定到刚才创建的IOCP上
    AConnect := accept( FListenSock, addr, len);
    CreateIoCompletionPort( AConnect, FCompletPort, nil, 0 );

    //创建CPU数*2 + 2个线程
    for i:=1 to si.dwNumberOfProcessors*2+2 do
    begin
      AThread
    := TRecvSendThread.Create( false );
      
    AThread.CompletPort := FCompletPort;
    //告诉这个线程,你要去这个IOCP去访问数据
    end;

    OK,就这么简单,我们要做的就是建立一个IOCP,把远程连接的socket句柄绑定到刚才创建的IOCP上,最后创建n个线程,并告诉这n个线程到这个IOCP上去访问数据就可以了。

    再看一下TRecvSendThread线程都干些什么:

    procedure TRecvSendThread
    .Execute;
    var
      
    ......
    begin
      
    while (not self.Terminated) do
      
    begin
        
    //查询IOCP状态(数据读写操作是否完成)
        
    GetQueuedCompletionStatus( CompletPort, BytesTransd, CompletKey, POVERLAPPED(pPerIoDat), TIME_OUT );

        
    if BytesTransd <> 0  then
          
    ....;
    //数据读写操作完成

        //再投递一个读数据请求
        
    WSARecv( CompletKey, @(pPerIoDat^.BufData), 1, BytesRecv, Flags, @(pPerIoDat^.Overlap), nil );
      
    end;
    end;

    读写线程只是简单地检查IOCP是否完成了我们投递的读写操作,如果完成了则再投递一个新的读写请求。
    应该注意到,我们创建的所有TRecvSendThread都在访问同一个IOCP(因为我们只创建了一个IOCP),并且我们没有使用临界区!难道不会产生冲突吗?不用考虑同步问题吗?
    呵呵,这正是IOCP的奥妙所在。IOCP不是一个普通的对象,不需要考虑线程安全问题。它会自动调配访问它的线程:如果某个socket上有一个线程A正在访问,那么线程B的访问请求会被分配到另外一个socket。这一切都是由系统自动调配的,我们无需过问。
  • 相关阅读:
    关于这个 blog
    P6499 [COCI2016-2017#2] Burza 题解
    CF1172F Nauuo and Bug 题解
    CF1479D Odd Mineral Resource 题解
    CF1442E Black, White and Grey Tree 题解
    CF1442D Sum 题解
    CF1025D Recovering BST 题解
    CF1056E Check Transcription 题解
    CF1025F Disjoint Triangles 题解
    红包算法的PHP实现
  • 原文地址:https://www.cnblogs.com/lancidie/p/2006437.html
Copyright © 2011-2022 走看看