代码改变世界

boost.asio的跨平台实现

2011-04-12 18:11  leo_hello  阅读(3205)  评论(0编辑  收藏  举报

前几天写了篇日志<<关于boost.asio的一点备忘>>,主要是基于其在linux平台下的实现写的,今天还是一篇备忘,根据boost.asio在win与非win下的实现来谈谈它的跨平台设计与实现.

boost.asio根据系统平台提供的事件多路分发机制来实现proactor模式,实现的差异化主要体现在支持IOCP机制与支持其它分发机制,下面就基于boost.asio在win系统(支持IOCP)与linux系统(支持epoll)下的实现来谈谈它的跨平台设计,非win系统的差异化主要表现在对reactor(系统提供的IO事件多路分发机制,如epoll,kqueue等)的封装上面.

首先回顾一下系统提供给我们的IO多路分发机制:

epoll:用户注册自己的socket及感兴趣的事件到epoll,然后调用epoll_wait,函数返回就绪了的IO事件源,用户然后可以对这些就绪的集合进行操作,其内部实现改变以前select的轮询机制,采用了更高效的实现机制(可能是回调函数...);

IOCP:用户注册自己的socket到完成端口,然后发起异步操作,用户可以创建一定数目的IOCP工作线程,工作线程调用GetQueuedCompletionStatus获得完成了的事件,这里与epoll机制有个显著的区别:epoll返回的是就绪操作,说明可以对socket进行真正的IO操作了,而完成端口返回的是IO事件完成的操作,即真正的IO操作已经完成了.

再来看看boost.iocp提供的服务:用户创建网络服务io_service(具体实现不用管),创建具体IO对象(stream_socket,timer...)并关联到网络服务,发起异步IO操作并注册回调函数,同时用户提供工作线程调用io_service的run函数(或poll,run_one...),当IO事件完成时,用户注册的回调函数将在这些工作线程中运行,注意,此时IO事件是已经完成了的.是不是觉得和IOCP机制很像?因为它们的实现都采用了proactor模式,所以boost.asio在win下的实现所做的工作比其它系统要少一些,同时,利用IOCP实现的server应该是很容易移植到boost.asio的,因为原理都一样.

OK,再来看看boost.asio是怎样设计达到跨平台的.

各种平台的差异主要是提供的IO分发机制不一样,boost.asio用io_service这个类来提供统一的网络服务接口,而在内部,根据平台的不同采用不同的实现:

#if defined(BOOST_ASIO_HAS_IOCP)
namespace detail { typedef win_iocp_io_service io_service_impl; }
#else
namespace detail { typedef task_io_service io_service_impl; }
#endif

io_service的接口就是对上面提供的实现的接口的一层封装,再来看看这两个实现在各自的平台下都做了些上面工作.

win_iocp_io_service:win下的网络服务封装类,内部采用IOCP实现,由于boost.asio与IOCP模式的相同性,很多工作都是系统代做了,比如工作线程的管理,实际IO操作的完成等,这个类只要在外层再做一些服务封装,使之和boost.asio提供的接口匹配起来.如果我们采用原生的IOCP机制,我们需要提供一个线程函数,在这个线程函数里面调用GetQueuedCompletionStatus,当这个函数成功返回时,根据完成的IO事件采取进一步的工作,线程的管理交给系统完成,如果采用boost.asio,我们需要提供一个回调函数,boost.asio会在它提供的函数(我们的线程必须调用这个函数)帮我们调用GetQueuedCompletionStatus,当IO事件完成后,调用我们提供的回调函数.可以认为boost.asio帮我们做了一些事情,但机制是一样的.

task_io_service:非win下的网络服务封装类,由于这些系统都只提供了同步事件多路分发机制,所以这个类需要多在这个机制的基础上提供额外的服务,使之对用户提供异步IO多路分发的服务,所以不可避免的需要管理用户提交的工作线程.我们来看一下如果在linux下(epoll机制),boost.asio应该做一些什么工作来达到这样的目的.首先task_io_service必须采用epoll机制取得就绪的事件.当发现IO事件就绪后,需要帮我们完成真正的IO事件,同时,必须管理用户提交的工作线程来运行用户提供的回调函数,这些工作线程之间的同步,比如何时休眠,何时工作都是由它来协调管理.task_io_service就是做了上面这些事情.不同的非win平台提供的同步事件多路分发机制也是不一样的,比如有select,epoll,kequeue,所以task_io_service在内部将采用系统提供的多路分发机制取得就绪事件并完成IO事件进行了一层封装,封装成xxx_reactor,如下:

#if defined(BOOST_ASIO_HAS_IOCP)
typedef select_reactor reactor;
#elif defined(BOOST_ASIO_HAS_EPOLL)
typedef epoll_reactor reactor;
#elif defined(BOOST_ASIO_HAS_KQUEUE)
typedef kqueue_reactor reactor;
#elif defined(BOOST_ASIO_HAS_DEV_POLL)
typedef dev_poll_reactor reactor;
#else
typedef select_reactor reactor;
#endif

xxx_reactor根据不同的平台采用不同的机制,而task_io_service只需要调用它的接口就达到了跨平台的目的.task_io_service只需要管理回调函数队列,协调用户提交的工作线程等.

boost.asio提供一些IO对象给用户,比如basic_stream_socket<tcp> socket,basic_socket_acceptor<tcp> acceptor,在这些对象上面提供异步的IO操作,这些操作最后都要提交到我们上面所说的网络服务封装(win_iocp_io_service或task_io_service)类进行统一管理,这一步在不同平台下差异化会比较大,比如在win下,我们可能只需要调用一下系统提供的的异步IO函数如WSARecv即可,而在linux下,我们需要将这个异步操作转化为同步操作,用同步操作来模拟异步操作,所以boost.asio将提交的这一步根据不同的平台封装成两个类,同时会对用户提供的回调函数进行进一步封装,以使这个回调函数根据发起的IO操作不同来帮我们完成一些事情,比如用户发起异步IO操作,boost.asio会对用户提交的回调函数进行进一步封装,使它还能帮用户完成实际的IO操作.两个不同的提交行为封装类如下:

win_iocp_socket_service:内部会调用系统提供的异步回调函数,同时把用户提交的函数对象提交到对应的IOobject;

reactive_socket_service:内部调用xxx_reactor的接口,提交用户提供的回调函数到xxx_reactor,xxx_reactor完成相应的工作(将回调函数添加到per descripter data).

boost.asio将系统提供的差异化服务封装到不同的类,并在上层抽象成统一的接口实现了跨平台.boost.asio的行为模式和IOCP类似,在非win平台下,它通过调用系统提供的同步事件多路分发服务,并协调管理用户提交的线程来达到同样的行为模式.

接下来有空的时候更新一些细节方面的东西,更多集中在应用方面,边实践边记录...