(本文基于Boost 1.69)
Boost.Asio代码风格。Asio为了可读性将部分较复杂的类的声明和实现分红了两个头文件,在声明的头文件末尾include负责实现的头文件。impl文件夹包含这些实现的头文件。另外,还有一个常见的关键词是detail,不一样操做系统下的各类具体的代码会放在detail文件夹下。
先看io_context
。io_context
的主要功能:接受任务,处理io事件,回调。
阅读源码发现,io_context
包含一个io_context具体实现,在windows平台是win_iocp_io_context
,实现了原生的proactor,其余平台则是一个scheduler
。react
// file: <boost/asio/io_context.hpp> ... #if defined(BOOST_ASIO_HAS_IOCP) typedef class win_iocp_io_context io_context_impl; class win_iocp_overlapped_ptr; #else typedef class scheduler io_context_impl; #endif ...
咱们来看看scheduler
。scheduler
包含一个reactor
,scheduler经过reactor模拟proactor:用户面对的接口一致,但数据的复制是在用户态而非内核态完成。
题外话:linux也有异步io接口,但相比windows iocp并不成熟。另外浏览源码可知,reactor在不一样平台的实现也不一样,其中linux实现为基于epoll
的epoll_reactor
,macOS实现为基于kqueue
的kqueue_reactor
。linux
// file: <boost/asio/detail/reactor_fwd.hpp> ... #if defined(BOOST_ASIO_HAS_IOCP) || defined(BOOST_ASIO_WINDOWS_RUNTIME) typedef class null_reactor reactor; #elif defined(BOOST_ASIO_HAS_IOCP) typedef class select_reactor reactor; #elif defined(BOOST_ASIO_HAS_EPOLL) typedef class epoll_reactor reactor; #elif defined(BOOST_ASIO_HAS_KQUEUE) typedef class kqueue_reactor reactor; #elif defined(BOOST_ASIO_HAS_DEV_POLL) typedef class dev_poll_reactor reactor; #else typedef class select_reactor reactor; #endif ...
scheduler
调度任务。首先介绍与scheduler密切相关的变量:windows
scheduler
成员op_queue_
:操做队列scheduler
成员task_
:reactor与scheduler
相关的队列有两个,其中scheduler
的操做队列用于存放通常性操做,而线程私有队列存放与reactor直接相关的操做。这两类操做须要分别处理,分开放在两个队列。查看Asio源码可知,scheduler::run
调用 scheduler::do_run_once
,其中reactor的成员函数run
接受线程私有队列。app
// file: <boost/asio/detail/impl/scheduler.ipp> ... std::size_t scheduler::do_run_one(mutex::scoped_lock& lock, scheduler::thread_info& this_thread, const boost::system::error_code& ec) { while (!stopped_) { if (!op_queue_.empty()) { // Prepare to execute first handler from queue. operation* o = op_queue_.front(); op_queue_.pop(); bool more_handlers = (!op_queue_.empty()); if (o == &task_operation_) { task_interrupted_ = more_handlers; if (more_handlers && !one_thread_) wakeup_event_.unlock_and_signal_one(lock); else lock.unlock(); task_cleanup on_exit = { this, &lock, &this_thread }; (void)on_exit; // Run the task. May throw an exception. Only block if the operation // queue is empty and we're not polling, otherwise we want to return // as soon as possible. task_->run(more_handlers ? 0 : -1, this_thread.private_op_queue); } else { std::size_t task_result = o->task_result_; if (more_handlers && !one_thread_) wake_one_thread_and_unlock(lock); else lock.unlock(); // Ensure the count of outstanding work is decremented on block exit. work_cleanup on_exit = { this, &lock, &this_thread }; (void)on_exit; // Complete the operation. May throw an exception. Deletes the object. o->complete(this, ec, task_result); return 1; } } else { wakeup_event_.clear(lock); wakeup_event_.wait(lock); } } return 0; } ...
通常性的operation能够直接传给scheduler
,可是reactor是scheduler
的一个私有成员,那么用户如何让reactor完成相关io操做,或者说,用户如何生成一个针对reactor的operation(还须要考虑跨平台的问题,在windows平台是针对基于iocp的proactor的operation)?这里须要一个额外的抽象,具体来讲,Asio经过服务来提供某类型的通常性接口。例如,查看Asio源码可知,unix平台下的reactive_socket_service
和windows平台的win_iocp_socket_service
就实现了基于reactor/proactor的io操做的接口,包括建立socket,异步发送、接收等,其中Handler为io操做完成后的回调类。有了这些服务,ip::tcp::socket
、ip::udp::socket
等具体实现只要调用service统一的接口来间接操做reactor/proactor便可。异步
// file: <boost/asio/detail/reactive_descriptor_service.hpp> template <typename Protocol> class reactive_socket_service : public service_base<reactive_socket_service<Protocol> >,public reactive_socket_service_base { ... template <typename Handler> void async_send_to(implementation_type& impl, const null_buffers&, const endpoint_type&, socket_base::message_flags, Handler& handler) ... } // file: <boost/asio/detail/win_iocp_socket_service.hpp> template <typename Protocol> class win_iocp_socket_service :public service_base<win_iocp_socket_service<Protocol> >,public win_iocp_socket_service_base { ... template <typename Handler> void async_send_to(implementation_type& impl, const null_buffers&,const endpoint_type&, socket_base::message_flags, Handler& handler) ... }
服务“模式”的具体实现:服务访问io_context内部的方式和注册服务。
服务访问io_context内部具体实现。Asio经过friend声明让其余symbol来访问io_context的私有成员以及io_context私有成员scheduler的私有成员
注册服务简介。Asio包含一个注册服务类service_registry。查看Asio源码可知,service_registry包含一个service链表,service_registry的owner(其中execution_context是io_context的基类)。socket
// file: <boost/asio/detail/service_registry.hpp> class service_registry: private noncopyable { public: // Constructor. BOOST_ASIO_DECL service_registry(execution_context& owner); ... private: ... // The owner of this service registry and the services it contains. execution_context& owner_; // The first service in the list of contained services. execution_context::service* first_service_; };
注册服务具体实现。显而易见地,围绕服务须要实现的功能有:
获取服务与注册服务。显然用户最关心的是如何方便的获取服务。用户获取服务时提供的参数是服务的类(做为函数模版的模版参数),查看源码可知,asio经过typeid
或服务类的静态数据成员id
的地址将类转化为具体的key,经过key进行服务的注册和查找。
生成服务。服务的构造参数为io_context,在堆上构造而后保存指针便可,这部分比较简单,很少做叙述。async
// file: <boost/asio/execution_context.hpp> ... struct key { key() : type_info_(0), id_(0) {} const std::type_info* type_info_; const execution_context::id* id_; } key_; ... // file: <boost/asio/detail/impl/service_registry.hpp> ... template <typename Service> void service_registry::init_key_from_id(execution_context::service::key& key,const service_id<Service>& /*id*/) { key.type_info_ = &typeid(typeid_wrapper<Service>); key.id_ = 0; } ... // file: <boost/asio/detail/impl/service_registry.ipp> ... void service_registry::init_key_from_id(execution_context::service::key& key,const execution_context::id& id) { key.type_info_ = 0; key.id_ = &id; } ...
为何使用服务“模式”?显然的,Asio的设计受到了proactor/reactor(跨平台)自己的影响,额外的抽象对于处理跨平台是颇有必要的。服务模式使得Asio库的接口更具备组织性,层次性,灵活性。具体来讲:tcp
ip::tcp::socket
和io_context
的公开成员函数,io_context
内部proactor实现对用户隐藏ip::tcp::socket
、ip::udp::socket
等的开发人员经过相关service提供的成员函数间接与proactor/reactor交互来实现各类io功能其余方案?笔者目前水平有限,暂时想不到更好的方法。函数