天天看點

boost asio ——深入架構

要用好它,就必須先了解它,而且不能停止于表面,必須深入到内部。而了解一件事物,先要了解它的架構,再了解它的細節。了解了架構,我們就有了提綱挈領的認識。

關于 boost asio 架構結構,在其文檔中,用了這樣一張圖來描述:

boost asio ——深入架構

簡單解釋一下:

這裡由使用者(Initiator)啟動一個異步操作(Asynchronous Operation),在啟動異步的同時它要負責建立一個異步回調對象(Completion Handler),然後該異步操作被交給了異步操作執行者(Asynchronous Operation Processor),由它負責執行異步操作,并在完成後将一個完成事件插入完成事件隊列(Completion Event Queue);另一方面,前攝器(Proactor,這個詞很難準确翻譯,也有翻譯為主動器,可能借義于proactive)驅動異步事件分派器(Asynchronous Event Demultiplexer)從完成事件隊列中擷取事件,這是一個阻塞的過程,一旦擷取到完成事件,前攝器從事件上找出與該事件關聯的回調對象,并執行回調。

這是一個标準的前攝器模式,這個模式是在 ACE 網絡庫中使用的概念。關于該模式的研究很多,搜尋一下 ACE Proactor 就可以找到很多資料。上面的描述也比較容易了解,唯一比較難搞懂的是異步事件分派器(Asynchronous Event Demultiplexer),好像它的存在并不起多大作用,其實它的作用大着呢,特别是在多線程中,它要保證異步完成事件的及時分派,提高多線程并發度,以及降低線程切換開銷。在 windows 完成端口的文檔中有這方面的機制介紹。

總得來說,這是一個概念性的模型,僅用這個模型來描述 boost asio,根本展現不了 boost asio 的優點。即使從使用者的角度,僅掌握這樣的模型也是不夠,boost asio 還有很多值得學習借鑒的地方。

我們需要結合這個模型來深入 boost asio 的實作架構。

 boost asio 是如何實作前攝器模式的呢?我們使用 boost asio 第一步都需要建立一個 boost::asio::io_service,我們就從 io_service 開始開啟我們的探秘之旅。

 io_service 類的定義很簡單,總共就三個成員變量:

[cpp]  view plain copy

  1. #if defined(BOOST_WINDOWS) || defined(__CYGWIN__)  
  2.   detail::winsock_init<> init_;  
  3. #elif defined(__sun) || defined(__QNX__) || defined(__hpux) || defined(_AIX) \  
  4.   || defined(__osf__)  
  5.   detail::signal_init<> init_;  
  6. #endif  
  7.   // The service registry.  
  8.   boost::asio::detail::service_registry* service_registry_;  
  9.   // The implementation.  
  10.   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

  1. inline std::size_t io_service::run(boost::system::error_code& ec)  
  2. {  
  3.   return impl_.run(ec);  
  4. }  
  5. inline std::size_t io_service::poll(boost::system::error_code& ec)  
  6.   return impl_.poll(ec);  
  7. inline void io_service::stop()  
  8.   impl_.stop();  
  9. inline void io_service::reset()  
  10.   impl_.reset();  
  11. template <typename Handler>  
  12. inline void io_service::dispatch(Handler handler)  
  13.   impl_.dispatch(handler);  
  14. inline void io_service::post(Handler handler)  
  15.   impl_.post(handler);  

但是 impl_type 是什麼呢?它的定義如下:

  1. #if defined(BOOST_ASIO_HAS_IOCP)  
  2.   typedef detail::win_iocp_io_service impl_type;  
  3. #elif defined(BOOST_ASIO_HAS_EPOLL)  
  4.   typedef detail::task_io_service<detail::epoll_reactor<false> > impl_type;  
  5. #elif defined(BOOST_ASIO_HAS_KQUEUE)  
  6.   typedef detail::task_io_service<detail::kqueue_reactor<false> > impl_type;  
  7. #elif defined(BOOST_ASIO_HAS_DEV_POLL)  
  8.   typedef detail::task_io_service<detail::dev_poll_reactor<false> > impl_type;  
  9. #else  
  10.   typedef detail::task_io_service<detail::select_reactor<false> > impl_type;  

原來是根據不同的平台支援特性,選擇了不同的實作,要把這麼多種實作融合起來,沒有一個很好的架構是很難做到的。

我們再來看看 service_registry_,對這個成員變量的使用展現在 use_service、add_service、has_service 三個函數中:

  1. template <typename Service>  
  2. inline Service& use_service(io_service& ios)  
  3.   // Check that Service meets the necessary type requirements.  
  4.   (void)static_cast<io_service::service*>(static_cast<Service*>(0));  
  5.   (void)static_cast<const io_service::id*>(&Service::id);  
  6.   return ios.service_registry_->template use_service<Service>();  
  7. void add_service(io_service& ios, Service* svc)  
  8.   if (&ios != &svc->io_service())  
  9.     boost::throw_exception(invalid_service_owner());  
  10.   if (!ios.service_registry_->template add_service<Service>(svc))  
  11.     boost::throw_exception(service_already_exists());  
  12. bool has_service(io_service& ios)  
  13.   return ios.service_registry_->template has_service<Service>();  

看起來是個集合容器,裡面的元素是服務(Service),服務有編号(id)。從 service_registry 的實作可以進一步了解到下面細節:

  1. 服務都是用一個類來實作的,唯一的接口要求是必須有 shutdown_service 接口
  2. 服務是延遲建立的,隻有第一次被使用的時候才建立
  3. 每種類型的服務在同一個 service_registry (也是同一個io_service)裡面最多隻有一個執行個體
  4. 當service_registry (也就是io_service)被銷毀(析構)時,服務會被 shutdown ,然後被銷毀

 繼續分析每個服務,還可以看到服務有下列特性:

  1. 服務在構造後就開始運作
  2. 服務在 shutdown 後停止運作
  3. 服務通過句柄(implementation_type)對外暴露其功能
  4. 服務之間有依賴性

最後,總結出所有的服務大概分為三類:

第一類服務是底層系統服務,是對作業系統平台提供功能的封裝,有:

  • 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 這一端。這就是上層使用者角度看到的景象。