1. 程式人生 > >boost::asio---深入框架

boost::asio---深入框架

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

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

proactor

簡單解釋一下:

這裡由使用者(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 類的定義很簡單,總共就三個成員變數:

  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. {  
  7.   return impl_.poll(ec);  
  8. }  
  9. inlinevoid io_service::stop()  
  10. {  
  11.   impl_.stop();  
  12. }  
  13. inlinevoid io_service::reset()  
  14. {  
  15.   impl_.reset();  
  16. }  
  17. template <typename Handler>  
  18. inlinevoid io_service::dispatch(Handler handler)  
  19. {  
  20.   impl_.dispatch(handler);  
  21. }  
  22. template <typename Handler>  
  23. inlinevoid io_service::post(Handler handler)  
  24. {  
  25.   impl_.post(handler);  
  26. }  

但是 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;  
  11. #endif


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

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

  1. template <typename Service>  
  2. inline Service& use_service(io_service& ios)  
  3. {  
  4.   // Check that Service meets the necessary type requirements.
  5.   (void)static_cast<io_service::service*>(static_cast<Service*>(0));  
  6.   (void)static_cast<const io_service::id*>(&Service::id);  
  7.   return ios.service_registry_->template use_service<Service>();  
  8. }  
  9. template <typename Service>  
  10. void add_service(io_service& ios, Service* svc)  
  11. {  
  12.   // Check that Service meets the necessary type requirements.
  13.   (void)static_cast<io_service::service*>(static_cast<Service*>(0));  
  14.   (void)static_cast<const io_service::id*>(&Service::id);  
  15.   if (&ios != &svc->io_service())  
  16.     boost::throw_exception(invalid_service_owner());  
  17.   if (!ios.service_registry_->template add_service<Service>(svc))  
  18.     boost::throw_exception(service_already_exists());  
  19. }  
  20. template <typename Service>  
  21. bool has_service(io_service& ios)  
  22. {  
  23.   // Check that Service meets the necessary type requirements.
  24.   (void)static_cast<io_service::service*>(static_cast<Service*>(0));  
  25.   (void)static_cast<const io_service::id*>(&Service::id);  
  26.   return ios.service_registry_->template has_service<Service>();  
  27. }  


看起來是個集合容器,裡面的元素是服務(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 這一端。這就是上層使用者角度看到的景象。