Boost.Asio源碼閱讀(1): 組織結構

(本文基於Boost 1.69)
Boost.Asio代碼風格。Asio爲了可讀性將部分較複雜的類的聲明和實現分紅了兩個頭文件,在聲明的頭文件末尾include負責實現的頭文件。impl文件夾包含這些實現的頭文件。另外,還有一個常見的關鍵詞是detail,不一樣操做系統下的各類具體的代碼會放在detail文件夾下。
先看io_contextio_context的主要功能:接受任務,處理io事件,回調。
閱讀源碼發現,io_context包含一個io_context具體實現,在windows平臺是win_iocp_io_context,實現了原生的proactor,其餘平臺則是一個schedulerreact

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

// 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::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的基類)。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::socketio_context的公開成員函數,io_context內部proactor實現對用戶隱藏
  • 負責ip::tcp::socketip::udp::socket等的開發人員經過相關service提供的成員函數間接與proactor/reactor交互來實現各類io功能
  • 若是有額外的某種服務需求,能夠手動實現某種service(不一樣操做系統可能須要不一樣實現)

其餘方案?筆者目前水平有限,暫時想不到更好的方法。函數

相關文章
相關標籤/搜索