Boost::asio核心的概念和功能
asio
的最核心的功能是用于異步的IO通信, 比如通過檔案 網絡或者控制台等.
asio
提供了一系列的工具來處理這種長時間的IO操作, 而且執行這些操作不需要依賴線程和鎖的模型.
筆記參考自官方文檔: https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview.html
基礎的結構
參考自: https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/basics.html
同步IO模型
程式執行同步IO的模型:

在boost.asio中,上述分别的對應接口如下:
io_service
表示程式與作業系統IO服務的連結,對應的boost接口
io_object
相當于一個IO對象, 比如socket的,這在boost中,有很多例子,比如
在類unix系統中, 操作失敗都有對應的錯誤碼,boost也是如此, 上述的同步連結中
boost::system::error_code ec;
socket.connect(server_endpoint, ec);
失敗的話,
ec
會儲存對應的錯誤碼, 該函數會阻塞, 直到建立連接配接成功或者失敗, 這也是同步IO的特點.
異步IO模型
異步IO模型圖
異步IO最明顯的一個特點是, 進行IO時,不需要阻塞等待傳回, 而是可以先執行其它的任務, 然後去讀取IO的内容.
程式可能的步驟是:
初始化IO對象
其中
your_completion_handler
的原型是
如果有參數, 可以是
bind
操作, 進行參數綁定操作.
作業系統執行IO的時候, 程式可以進行其它的操作, OS會把有關的結果放到就緒隊列中, 等待
io_context
讀取. 讀取操作在
your_completion_handler
執行, 需要調用
io_context::run()
來啟動.
Proactor設計模式
Linux中, 有個很經典的Reactor模型, 這是指有一個主線程負責監聽IO事件, 然後把産生IO的檔案描述符和對應的操作傳遞給一個線程中, 由線程池進行驅動. 是以, Reactor模式需要一個主線程和若幹個線程.
而Proactor模式一般隻需要一個線程即可. 先給出Proactor模式的示例圖:
- Asynchronous Operation: 異步操作, 比如對socket的讀寫
- Asynchronous Operation Processor: 執行異步操作, 同時把讀寫就緒事件放到就緒隊列中
- Completion Event Queue: 存儲讀寫就緒事件
- Completion Handler: 使用者自定義的處理IO的函數, 一般需要借助
操作bind
- Asynchronous Event Demultiplexer: 在Completion Event Queue上阻塞等待, 把就緒事件傳回給它的調用者.
- Proactor: 調用Asynchronous Event Demultiplexer來使得就緒事件出隊, 之後把就緒事件配置設定給相應的處理句柄, 這由io_service來完成.
- Initiator: 啟動整個異步操作流程.
strand, 使用無鎖的線程
官方文檔: https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/strands.html
一個strand可以了解為: 一系列嚴格有序的操作, 通過strand操作, 可以讓這一系列的操作在多線程中不顯式地加鎖執行.
strand
分為顯式和隐式兩種, 對應三種情況:
- 調用
函數, 這是隐式執行, 所有的操作序列都在一個線程中執行, 可定不用加鎖io_context::run()
- 一組connect操作, 比如HTTP連接配接, 這是隐式操作
- 通過
或者strand<>
顯式說明, 所用的事件處理的函數對象需要通過io_context:strand
綁定, 否則需要boost::asio::bind_executor()
對象進行分發操作.strand
很多時候, 我們面對的是異步操作的組合, 這些組合構成的操作通過一個
strand
. 那麼, 我們需要保證跨線程時, 調用者和操作者處于不同線程時, 要有足夠的安全性, 通過下面的函數擷取
executor
, 比如:
Buffer和流的操作
因為IO操作的資料需要經過記憶體, 那麼必然有buffer的概念, 具體可以查找手冊進行處理. 流式的資料, 也需要借助有關的流操作實作, 具體可以參考手冊.
Reator模式的操作
借助第三方庫等完成操作.
https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/reactor.html
Line-Based資料模式操作
相當于是資料流的結束符, 因為很多是面向流的資料.
https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/line_based.html
調試追蹤異步操作
主要是為了異步調試友善, 具體參考手冊:
https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/handler_tracking.html
關于協程
可以了解為一個更加輕量級的線程, 具體操作手冊:
- https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/concurrency_hint.html
- https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/coroutine.html
- https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/spawn.html
網絡程式設計
-
支援TCP UDP和ICMP通信
https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/networking/protocols.html
- 其他協定 https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/networking/other_protocols.html
- BSD核心與boost對應的函數: https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/networking/bsd_sockets.html
定時器
對于長時間的IO操作, 定時器是至關重要的, Linux的C語言系統調用函數中, 主要有三種類型的定時模式, 分别是:
-
的逾時機制,對應筆記: https://blog.csdn.net/qq_35976351/article/details/86530979socket
-
信号操作, https://blog.csdn.net/qq_35976351/article/details/86532889SIGALARM
- IO複用函數的逾時機制,比如
函數, https://blog.csdn.net/qq_35976351/article/details/86535924epoll_wait
但是, 在boost::asio中, 有與異步IO更比對的定時器機制. 直接參考文檔:
https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/timers.html
一般的使用方式為:
void handler(boost::system::error_code ec) { ... }
// do something here
io_context i;
// do something here
deadline_timer t(i);
t.expires_from_now(boost::posix_time::milliseconds(400));
t.async_wait(handler);
// do something here
i.run();
信号處理機制
先回顧Linux的信号處理機制: https://blog.csdn.net/qq_35976351/article/details/85524540
給出一個一般的解決方案, 參考自: https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/signals.html
void handler(
const boost::system::error_code& error,
int signal_number)
{
if (!error)
{
// A signal occurred.
}
}
// Construct a signal set registered for process termination.
boost::asio::signal_set signals(io_context, SIGINT, SIGTERM);
// Start an asynchronous wait for one of the signals to occur.
signals.async_wait(handler);