Boost.Asio源码阅读(1): 组织结构

Don_Quijote

(本文基于Boost 1.69)
Boost.Asio代码风格。Asio为了可读性将部分较复杂的类的声明和实现分成了两个头文件,在声明的头文件末尾include负责实现的头文件。impl文件夹包含这些实现的头文件。另外,还有一个常见的关键词是detail,不同操作系统下的各种具体的代码会放在detail文件夹下。
先看io_contextio_context的主要功能:接受任务,处理io事件,回调。
阅读源码发现,io_context包含一个io_context具体实现,在windows平台是win_iocp_io_context,实现了原生的proactor,其他平台则是一个scheduler

// 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
...

我们来看看schedulerscheduler包含一个reactor,scheduler通过reactor模拟proactor:用户面对的接口一致,但数据的复制是在用户态而非内核态完成。
题外话:linux也有异步io接口,但相比windows iocp并不成熟。另外浏览源码可知,reactor在不同平台的实现也不同,其中linux实现为基于epollepoll_reactor,macOS实现为基于kqueuekqueue_reactor

// 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密切相关的变量:

  • scheduler成员op_queue_:操作队列
  • scheduler成员task_:reactor
  • 线程私有队列

scheduler相关的队列有两个,其中scheduler的操作队列用于存放一般性操作,而线程私有队列存放与reactor直接相关的操作。这两类操作需要分别处理,分开放在两个队列。查看Asio源码可知,scheduler::run调用 scheduler::do_run_once,其中reactor的成员函数run接受线程私有队列。

// 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::socketip::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的基类)。

// 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,在堆上构造然后保存指针即可,这部分比较简单,不多作叙述。

// 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库的接口更具有组织性,层次性,灵活性。具体来说:

  • 普通用户直接使用ip::tcp::socketio_context的公开成员函数,io_context内部proactor实现对用户隐藏
  • 负责ip::tcp::socketip::udp::socket等的开发人员通过相关service提供的成员函数间接与proactor/reactor交互来实现各种io功能
  • 如果有额外的某种服务需求,可以手动实现某种service(不同操作系统可能需要不同实现)

其他方案?笔者目前水平有限,暂时想不到更好的方法。

阅读 3.7k
4 声望
2 粉丝
0 条评论
4 声望
2 粉丝
文章目录
宣传栏