boost::asio---深入框架
要用好它,就必須先了解它,而且不能停止於表面,必須深入到內部。而瞭解一件事物,先要了解它的框架,再瞭解它的細節。瞭解了框架,我們就有了提綱挈領的認識。
關於 boost asio 框架結構,在其文件中,用了這樣一張圖來描述:
簡單解釋一下:
這裡由使用者(Initiator)啟動一個非同步操作(Asynchronous Operation),在啟動非同步的同時它要負責建立一個非同步回撥物件(Completion Handler),然後該非同步操作被交給了非同步操作執行者(Asynchronous Operation Processor),由它負責執行非同步操作,並在完成後將一個完成事件插入完成事件佇列
這是一個標準的前攝器模式,這個模式是在 ACE 網路庫中使用的概念。關於該模式的研究很多,搜尋一下 ACE Proactor 就可以找到很多資料。上面的描述也比較容易理解,唯一比較難搞懂的是非同步事件分派器(Asynchronous Event Demultiplexer)
總得來說,這是一個概念性的模型,僅用這個模型來描述 boost asio,根本體現不了 boost asio 的優點。即使從使用者的角度,僅掌握這樣的模型也是不夠,boost asio 還有很多值得學習借鑑的地方。
我們需要結合這個模型來深入 boost asio 的實現框架。
boost asio 是如何實現前攝器模式的呢?我們使用 boost asio 第一步都需要建立一個 boost::asio::io_service,我們就從 io_service 開始開啟我們的探祕之旅。
io_service 類的定義很簡單,總共就三個成員變數:
- #if defined(BOOST_WINDOWS) || defined(__CYGWIN__)
- detail::winsock_init<> init_;
- #elif defined(__sun) || defined(__QNX__) || defined(__hpux) || defined(_AIX) \
- || defined(__osf__)
- detail::signal_init<> init_;
- #endif
- // The service registry.
- boost::asio::detail::service_registry* service_registry_;
- // The implementation.
- impl_type& impl_;
其實簡單反而意味著強大,因為這表明 boost asio 已經把功能結構劃分的很清晰了。
三個成員變數中的 init_ 與結構沒有太大關係,windows 平臺的 winsock_init 裡面呼叫了 WSAStartup,linux 平臺的 signal_init 裡面遮蔽了 SIG_PIPE 訊號。這兩個東東幾乎是在我們剛開始接觸網路程式設計就遇到的知識點。
其次我們再來看看 impl_ 成員,從名字上看應該是 io_service 的實現類,確實 io_service 的很多介面是直接轉發給了 impl_ 成員,比如 run、poll、stop、reset、post、dispatch
- inline std::size_t io_service::run(boost::system::error_code& ec)
- {
- return impl_.run(ec);
- }
- inline std::size_t io_service::poll(boost::system::error_code& ec)
- {
- return impl_.poll(ec);
- }
- inlinevoid io_service::stop()
- {
- impl_.stop();
- }
- inlinevoid io_service::reset()
- {
- impl_.reset();
- }
- template <typename Handler>
- inlinevoid io_service::dispatch(Handler handler)
- {
- impl_.dispatch(handler);
- }
- template <typename Handler>
- inlinevoid io_service::post(Handler handler)
- {
- impl_.post(handler);
- }
但是 impl_type 是什麼呢?它的定義如下:
- #if defined(BOOST_ASIO_HAS_IOCP)
- typedef detail::win_iocp_io_service impl_type;
- #elif defined(BOOST_ASIO_HAS_EPOLL)
- typedef detail::task_io_service<detail::epoll_reactor<false> > impl_type;
- #elif defined(BOOST_ASIO_HAS_KQUEUE)
- typedef detail::task_io_service<detail::kqueue_reactor<false> > impl_type;
- #elif defined(BOOST_ASIO_HAS_DEV_POLL)
- typedef detail::task_io_service<detail::dev_poll_reactor<false> > impl_type;
- #else
- typedef detail::task_io_service<detail::select_reactor<false> > impl_type;
- #endif
原來是根據不同的平臺支援特性,選擇了不同的實現,要把這麼多種實現融合起來,沒有一個很好的架構是很難做到的。
我們再來看看 service_registry_,對這個成員變數的使用體現在 use_service、add_service、has_service 三個函式中:
- template <typename Service>
- inline Service& use_service(io_service& ios)
- {
- // Check that Service meets the necessary type requirements.
- (void)static_cast<io_service::service*>(static_cast<Service*>(0));
- (void)static_cast<const io_service::id*>(&Service::id);
- return ios.service_registry_->template use_service<Service>();
- }
- template <typename Service>
- void add_service(io_service& ios, Service* svc)
- {
- // Check that Service meets the necessary type requirements.
- (void)static_cast<io_service::service*>(static_cast<Service*>(0));
- (void)static_cast<const io_service::id*>(&Service::id);
- if (&ios != &svc->io_service())
- boost::throw_exception(invalid_service_owner());
- if (!ios.service_registry_->template add_service<Service>(svc))
- boost::throw_exception(service_already_exists());
- }
- template <typename Service>
- bool has_service(io_service& ios)
- {
- // Check that Service meets the necessary type requirements.
- (void)static_cast<io_service::service*>(static_cast<Service*>(0));
- (void)static_cast<const io_service::id*>(&Service::id);
- return ios.service_registry_->template has_service<Service>();
- }
看起來是個集合容器,裡面的元素是服務(Service),服務有編號(id)。從 service_registry 的實現可以進一步瞭解到下面細節:
- 服務都是用一個類來實現的,唯一的介面要求是必須有 shutdown_service 介面
- 服務是延遲建立的,只有第一次被使用的時候才建立
- 每種型別的服務在同一個 service_registry (也是同一個io_service)裡面最多隻有一個例項
- 當service_registry (也就是io_service)被銷燬(析構)時,服務會被 shutdown ,然後被銷燬
繼續分析每個服務,還可以看到服務有下列特性:
- 服務在構造後就開始運作
- 服務在 shutdown 後停止運作
- 服務通過控制代碼(implementation_type)對外暴露其功能
- 服務之間有依賴性
最後,總結出所有的服務大概分為三類:
第一類服務是底層系統服務,是對作業系統平臺提供功能的封裝,有:
- detail::win_iocp_io_service
- detail::win_iocp_socket_service
- detail::task_io_service
- detail::reactive_socket_service
- detail::epoll_reactor
- detail::kqueue_reactor
- detail::dev_poll_reactor
- detail::select_reactor
- detail::resolver_service
中間四個都是 reactor,不能想象,所有的 reactor 應該有相同的對外服務介面。這裡的 task_io_service 和 reactive_socket_service 是對 reactor 的再封裝,所以上層的服務不會直接依賴 reactor,而是依賴 task_io_service 和 reactive_socket_service。
第二類服務是上層介面服務,面向具體的功能物件(Object),他們會針對執行平臺選擇依賴對應的底層服務
- socket_acceptor_service(ip::basic_socket_acceptor -> ip::tcp::acceptor)
- stream_socket_service(ip::basic_stream_socket -> ip::tcp::socket)
- datagram_socket_service(ip::basic_datagram_socket -> ip::udp::socket)
- raw_socket_service(ip::basic_raw_socket -> ip::icmp::socket)
- deadline_timer_service(basic_deadline_timer -> deadline_timer)
- detail::deadline_timer_service
- ip::resolver_service(ip::basic_resolver -> ip::tcp::resolver, ip::udp::resolver, ip::icmp::resolver)
前四個 socket 相關的服務會在不同的平臺,選擇依賴 win_iocp_socket_service 和 reactive_socket_service<xxx_reactor>,deadline_timer_service (具體實現在
detail::deadline_timer_service中)會選擇 win_iocp_io_service 和 task_io_service<xxx_reactor>,ip::resolver_service 沒得選擇,只有依賴 detail::resolver_service。
第三類服務是一些特殊功能的服務,比如 detail::strand_service 等,他們對整體框架沒有影響。
雖然 boost asio 中提供了這麼多服務,但是上層應用並不會直接使用這些服務,服務通過控制代碼對外暴露其功能,而控制代碼被功能物件(Object)封裝,然後提供給上層應用使用。
這裡的功能物件(Object),就是我們在第二類服務後面的“()”裡面給出的類,每個物件都包含著一個對相應服務的C++引用,以及服務對外暴露的控制代碼。
至此,我們瞭解了 boost asio 中通過一系列的服務封裝了作業系統的底層功能,並且通過動態組裝的方式把這些服務管理起來。可以看出,boost asio 使用了一種相當簡單的方式,就解決了平臺的多樣性,甚至於模式的多樣性;同時服務的動態載入和集中管理,為功能擴充套件提供了方便途徑;另外物件中只包含控制代碼,提高了安全性。
再回過頭來看看,我們心中還有個疑問,那就是 boost asio 是怎麼實現前攝器(Proactor)模式的呢?其實前攝器(Proactor)的各個角色都是通過服務來表達的。
先看 windows 平臺
Asynchronous Operation Processor 的功能是由 win_iocp_socket_service、resolver_service 完成,Proactor 功能由 win_iocp_io_service 完成,win_iocp_io_service 也包含 Asynchronous Event Demultiplexer 和 Completion Event Queue 的功能,不過其實是對 windows IOCP 的系統功能的封裝。
再看看 linux 平臺
Asynchronous Operation Processor 的功能是由 reactive_socket_service、resolver_service 完成,Proactor、Asynchronous Event Demultiplexer 和 Completion Event Queue功能都是由 task_io_service 完成。
最後,io_service 其實代表了Proator 這一端,socket、timer、resolver 等等代表了 Initiator 這一端。這就是上層使用者角度看到的景象。