一、Asio
Boost 的 WebSocket 是基于 Boost 的异步 IO 库 Asio。Boost.Asio 是使用现代 C++ 技术为网络和 I/O 异步编程模型而做的库。
1、异步操作
异步操作表示在后台启动和执行的工作,而启动该工作的用户代码可以继续处理其他事情。initiating function 是一个可以由用户调用来启动异步操作的函数。一个 completion handler 是一个用户提供的,只允许移动的函数对象,它将被调用最多一次,在异步操作完成时被调用并以异步操作的结果为参数。
2、异步代理
异步代理是异步操作的顺序组合。每个异步操作都被认为是异步代理的一部分,即使该代理只包含单个操作。异步代理是可以与其他代理并发执行工作的实体。异步代理与异步操作的关系就像线程与同步操作的关系一样。
异步代理具有相关的特征,这些特征指定异步操作在组成该代理时应该如何表现,例如:
- Allocator,它决定代理的异步操作如何获取内存资源。
- Cancellation slot,它决定代理的异步操作如何支持取消。
- Executor,它决定代理的 completion handler 将如何排队和运行。
3、线程和 Boost.Asio
io_context 是线程安全的。
多个线程可以调用 io_context::run() 来建立一个线程池,其中可以调用 completion handler。所有加入 io_context 池的线程都被认为是相同的,io_context 可以以任意的方式在它们之间分配工作。
io_context 构造函数允许程序指定并发提示。这是对 io_context 实现关于应该用于运行 completion handler 的活动线程数量的建议。
4、Strands:使用线程不需要显示锁
一个 strand 被定义为事件处理程序的严格顺序调用(即没有并发调用)。使用 strand 允许在多线程程序中执行代码,而不需要显式锁定(例如使用互斥锁)。
链可以是隐式的,也可以是显式的,例如:
- 仅从一个线程调用 io_context::run() 意味着所有事件处理程序都在隐式链中执行,因为 io_context 保证处理程序只能从 run() 内部调用。
- 在与一个连接相关联的异步操作的单链中(例如,在像 HTTP 这样的半双工协议实现中),处理程序不可能并发执行。这是一条隐式链。
- 显式的 strand 是 strand<> 或 io_context::strand 的实例。所有事件处理函数对象都需要使用 boost::asio::bind_executor() 绑定到 strand 上,或者通过 strand 对象 posted/dispatched。
所有异步操作都通过使用 get_associated_executor 函数获得处理程序的关联 executor。例如:
boost::asio::associated_executor_t<Handler> a = boost::asio::get_associated_executor(h);
boost::asio::bind_executor() 函数是用于将特定的 executor 对象(比如一个 strand)绑定到一个completion handler。此绑定自动关联 executor:
my_socket.async_read_some(my_buffer,
boost::asio::bind_executor(my_strand,
[](error_code ec, size_t length)
{
// ...
}));
5、协程
spawn() 函数是用于运行 stackful coroutines 的高级包装器。spawn() 函数使程序能够以同步的方式实现异步逻辑,如下例所示:
boost::asio::spawn(my_strand, do_echo, boost::asio::detached);
// ...
void do_echo(boost::asio::yield_context yield)
{
try
{
char data[128];
for (;;)
{
std::size_t length =
my_socket.async_read_some(
boost::asio::buffer(data), yield);
boost::asio::async_write(my_socket,
boost::asio::buffer(data, length), yield);
}
}
catch (std::exception& e)
{
// ...
}
}
第一个参数可以是 executor 或 execution context。此参数确定允许协程执行的 context。
第二个参数是一个函数对象:
void coroutine(boost::asio::yield_context yield);
它指定要作为协程的一部分运行的代码。参数 yield 可以传递给异步操作来代替 completion handler。这将启动异步操作并挂起协程。当异步操作完成时,协程将自动恢复。
要从操作中收集 error_code,而不是让它抛出异常,请将输出变量与 yield_context 关联如下:
boost::system::error_code ec;
std::size_t length =
my_socket.async_read_some(
boost::asio::buffer(data), yield[ec]);
二、Boost.Beast.WebSocket
WebSocket 连接需要一个有状态对象,在 Beast 中由一个类模板 WebSocket::stream 表示。接口使用分层流模型。websocket 流对象包含另一个流对象,称为“next layer”,用于执行I/O。各模板参数说明如下:
namespace boost {
namespace beast {
namespace websocket {
template<
class NextLayer,
bool deflateSupported = true>
class stream;
} // websocket
} // beast
} // boost