异步IO框架实现之完成端口(Completion Port)

烽驿2009开源实时通信平台 源码获取:svn checkout http://fy2009.googlecode.com/svn/trunk/ fy2009-read-only

 

微软在Windows 2000之后提供了真正可伸缩(Scalable)的异步IO机制,即完成端口。本异步 IO框架(有关该框架的详细介绍请参:http://blog.csdn.net/DreamFreeLancer/archive/2009/07/26/4381316.aspx)在

Windows上提供了基于完成端口的实现。基于完成端口的编程在形式上非常类似于Linux下的EPOLL(http://blog.csdn.net/DreamFreeLancer/archive/2009/07/28/4387375.aspx)。在使用

完成端口之前,需调用函数:
HANDLE h_iocp = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
创建一个完成端口对象,用于后续的Socket绑定。这类似于Linux下调用epoll_create创建EPOLL对象描述

符。
在实现bool aio_provider_t::register_fd(aio_sap_it *dest_sap, int32 fd, sp_aioeh_t& eh)时通过调用::CreateIoCompletionPort((HANDLE)fd, h_iocp, 0, 0); 将Socket句柄fd和完成端口对象h_iocp绑定到一起。随后发生在这些Socket上的异步IO事件,将可通过h_iocp检索到。

如前面博文所述,完成端口和EPOLL等异步IO机制最大的不同在于前者属于”完成通知型“而后者属于”可用通知型“。

完成端口编程的基本思想是:你想收/发数据你就收/发,但不保证立即成功,成功时系统会通知

你; 而EPOLL等的编程思想则是:什么时候可以收/发了,系统会通知你。
完成端口编程的一项重要任务是扩展OVERLAPPED结构,以将足够的应用程序信息与异步IO事件绑定到一起。尽管

定义自己的OVERLAPPED结构时系统OVERLAPPED成员不一定要是自定义结构的第一个成员,但笔者坚持

认为那是一个好的实践,好处是在从完成端口上检索到IO事件时,可将其OVERLAPPED指针参数强转成自

定义OVERLAPPED指针,而不必担心任何结构成员的Offset问题。本项目自定义的OVERLAPPED结构如下:
typedef struct
{
 OVERLAPPED overlapped; //系统OVERLAPPED成员,作为第一个成员
 int32 fd; //用于标记异步IO事件所对应的Socket句柄
 //set to AIO_POLLIN by WSAAccept, WSARecv or set to AIO_POLLOUT by WSASend, or

//set to AIO_POLLERR by aio_provider
 uint32 aio_events;  
 WSABUF wsa_buf; //用于给WSARecv或WSASend提供收/发数据的参数

//实际完成收/发的字节数, 由aio_provider_t填充。修饰成volatile的目的是,根据应用需要切换Socket所属线程时,

//可能会丢失部分IO事件,但可在完成线程切换后通过主动检查该字段是否为零来判断IO是否已经完成。
 volatile uint32 transferred_bytes;
} FY_OVERLAPPED, *LPFY_OVERLAPPED;

 

实现int8 aio_provider_t::heart_beat()的主要内容如下:

uint32 bytes_transferred=0;
uint32 cp_key=0;
LPFY_OVERLAPPED p_op=NULL;

GetQueuedCompletionStatus(h_iocp, &bytes_transferred, &cp_key, 

    (LPOVERLAPPED *)(&p_op), TIMEOUT_MS);

类似于EPOLL中调用epoll_wait,调用上面的函数可监测所有注册到h_iocp上的Socket是否有IO完成事件发生,如果有,将可得到先前应用程序通过调用WSAAccept,WSASend或WSARecv提交到该Socket上的异步操作相关联的

FY_OVERLAPPED结构指针 

 if(p_op)
  {
     p_op->transferred_bytes = bytes_transferred;

     //这里Socket fd除以4以后用作事件处理器数组的下标fd_key,有关此转换算法的理论依据,请参笔者另一篇博文:

    //(http://blog.csdn.net/DreamFreeLancer/archive/2009/07/09/4335571.aspx)
     uint32 fd_key=(p_op->fd)>>2;

    if(!_ehs[fd_key].is_null())

    {

       //事件处理器实现将下面的p_op参数还原成LPFY_OVERLAPPED,从而检索出具体的事件类型及实现转成收/发的字

      //数,决定继续发送或复用或删除FY_OVERLAPPED结构.

     //pointer_box_t是一整型类型,但长度足够容量一个指针,32位OS就是4个字节; 64位OS将是8个字节
        _ehs[fd_key]->on_aio_events(p_op->fd, p_op->aio_events, (pointer_box_t)p_op);   

     }
  } 
根据前面有关异步IO框架的博文(http://blog.csdn.net/DreamFreeLancer/archive/2009/07/26/4381316.aspx)中的描述,我们知道heart_beat将运行在一个线程中,即所谓“心跳”线程,因此,在本框架实现中,完成端口允许多个并发线程同时等待同一完成端口的特性不会被使用,因本框架的设计目标主要是需要保持持久连接的场合,同一个Socket上的IO事件可能被分发到不同线程的情形是不能接受的(而完成端口的多线程等待恰恰会引起这样的问题),因此,本框架总是在一个线程(心跳线程)上等待完成端口上的IO事件,但对具体事件的处理允许被分派到不同线程(如果特定Socket上注册的事件处理器驻留在心跳线程之外)。即本异步IO框架将多线程分派从完成端口等待延迟到分辨出事件所属的Socket。这在需要丰富Session状态的持久连接场合更具合理性。

 

另外基于完成端口实现上述事件处理器时需注意以下几点:

1. WSASend和WSARecv调用即使返回”立即成功“,其对应的异步IO事件仍将被触发。因此,立即成功和PENDING状态不需要分别处理

2.WSASend和WSARecv调用前一定要将其flag参数置零,FY_OVERLAPPED中的系统OVERLAPPED成员也得置空

3.IO事件处理器实现应负责所有FY_OVERLAPPED变量的生存期管理,可以采用基于Stack或Heap的成员变量数组,以管理应用层的收/发缓冲区,方便实现应用层Flow Control等特性。

你可能感兴趣的:(多线程,编程,框架,IO,socket,events)