试图理解Boost.Asio自定义服务实现
Trying to understand Boost.Asio custom service implementation
我正在考虑在我们目前使用的现有专有第三方网络协议的基础上编写一个自定义Asio服务。
根据Highscore Asio指南,您需要实现三个类来创建自定义Asio服务:
- 从
boost::asio::basic_io_object
派生的类,表示新的I/O对象 - 从
boost::asio::io_service::service
派生的类,表示向I/O服务注册并且可以从I/O对象访问的服务 - 不是从表示服务实现的任何其他类派生的类
网络协议实现已经提供异步操作,并具有(阻塞)事件循环。所以我想,我应该把它放在我的服务实现类中,并在内部工作线程中运行事件循环。到目前为止还不错。
通过查看一些自定义服务的示例,我注意到服务类生成了自己的内部线程(事实上,它们实例化了自己的内置io_service实例)。例如:
-
Highscore页面提供了一个目录监视器示例。它本质上是对inotify的包装。有趣的类是
inotify/basic_dir_monitor_service.hpp
和inotify/dir_monitor_impl.hpp
。Dir_monitor_impl
处理与inofity的实际交互,inofity是阻塞的,因此在后台线程中运行。我同意这一点。但basic_dir_monitor_service
也有一个内部工作线程,所要做的似乎只是在main的io_service
和dir_monitor_impl
之间搅乱请求。我反复处理了代码,删除了basic_dir_monitor_service
中的工作线程,而是将请求直接发布到主io_service,程序仍然像以前一样运行。 -
在Asio的自定义记录器服务示例中,我注意到了同样的方法。
logger_service
生成一个内部工作线程来处理日志记录请求。我还没有时间处理这些代码,但我认为,应该也可以将这些请求直接发布到主io_service。
拥有这些"中介工作者"有什么好处?你不能一直把所有的工作都发布到主io_service吗?我是否错过了Proactor模式的一些关键方面?
我可能应该提到,我正在为一个功率不足的单核嵌入式系统编写软件。这些额外的线程似乎只是强加了不必要的上下文切换,如果可能的话,我希望避免这种情况。
简而言之,就是一致性。这些服务试图满足服务Boost提出的用户期望。Asio提供。
使用内部io_service
可以清楚地分离处理程序的所有权和控制权。如果自定义服务将其内部处理程序发布到用户的io_service
中,则服务的内部处理程序的执行将与用户的处理程序隐式耦合。考虑一下这将如何影响Boost对用户的期望。Asio Logger服务示例:
logger_service
写入处理程序中的文件流。因此,从不处理io_service
事件循环的程序,例如只使用同步API的程序,将永远不会写入日志消息logger_service
将不再是线程安全的,如果io_service
由多个线程处理,则可能会调用未定义的行为logger_service
的内部操作的生存期受到io_service
的生存期的限制。例如,当调用服务的shutdown_service()
函数时,拥有io_service
的生命周期已经结束。因此,无法通过shutdown_service()
中的logger_service::log()
记录消息,因为它将尝试将内部处理程序发布到其生存期已经结束的io_service
中-
用户可能不再假设操作和处理程序之间存在一对一映射。例如:
boost::asio::io_service io_service; debug_stream_socket socket(io_service); boost::asio::async_connect(socket, ..., &connect_handler); io_service.poll(); // Can no longer assume connect_handler has been invoked.
在这种情况下,
io_service.poll()
可以调用logger_service
内部的处理程序,而不是connect_handler()
。
此外,这些内部线程试图模仿Boost内部使用的行为。Asio本身:
用于特定平台的此库的实现可以使用一个或多个内部线程来模拟异步性。这些线程必须尽可能对库用户不可见。
目录监视器示例
在目录监视器示例中,内部线程用于防止在等待事件时无限期地阻塞用户的io_service
。一旦事件发生,就可以调用完成处理程序,因此内部线程将用户处理程序发布到用户的io_service
中,以进行延迟调用。这个实现通过一个内部线程来模拟异步性,该线程对用户来说基本上是不可见的。
有关详细信息,当通过dir_monitor::async_monitor()
启动异步监视器操作时,会将basic_dir_monitor_service::monitor_operation
发布到内部io_service
中。当被调用时,此操作会调用dir_monitor_impl::popfront_event()
,这是一个潜在的阻塞调用。因此,如果monitor_operation
被发布到用户的io_service
中,则用户的线程可能被无限期地阻塞。考虑对以下代码的影响:
boost::asio::io_service io_service;
boost::asio::dir_monitor dir_monitor(io_service);
dir_monitor.add_directory(dir_name);
// Post monitor_operation into io_service.
dir_monitor.async_monitor(...);
io_service.post(&user_handler);
io_service.run();
在上面的代码中,如果io_service.run()
首先调用monitor_operation
,那么user_handler()
将不会被调用,直到dir_monitor
在dir_name
目录上观察到事件为止。因此,dir_monitor
服务的实现不会以大多数用户期望的其他服务的一致方式运行。
Asio Logger服务
内部线程和io_service
:的使用
- 通过在内部线程中执行潜在的阻塞或昂贵的调用,减少了登录用户线程的开销
- 保证
std::ofstream
的线程安全,因为只有单个内部线程写入流。如果日志记录是直接在logger_service::log()
中完成的,或者如果logger_service
将其处理程序发布到用户的io_service
中,那么为了线程安全,将需要显式同步。其他同步机制可能会在实现中引入更大的开销或复杂性 允许
services
在shutdown_service()
中记录消息。在销毁过程中,io_service
将:- 关闭其每个服务
- 销毁
io_service
或其任何关联的strand
中计划延迟调用的所有未调用处理程序 - 销毁其每个服务
由于用户的io_service
的生存期已结束,因此既不处理其事件队列,也无法发布其他处理程序。通过具有由其自己的线程处理的其自己的内部io_service
,logger_service
使其他服务能够在其shutdown_service()
期间记录消息。
其他注意事项
在实现自定义服务时,需要考虑以下几点:
- 阻止内部线程上的所有信号
- 永远不要直接调用用户的代码
- 如何在实现被销毁时跟踪和发布用户处理程序
- 服务所拥有的、在服务实现之间共享的资源
对于最后两点,dir_monitor
I/O对象表现出用户可能没有预料到的行为。当服务中的单个线程调用单个实现的事件队列上的阻塞操作时,它有效地阻塞了可能立即完成的相应实现的操作:
boost::asio::io_service io_service;
boost::asio::dir_monitor dir_monitor1(io_service);
dir_monitor1.add_directory(dir_name1);
dir_monitor1.async_monitor(&handler_A);
boost::asio::dir_monitor dir_monitor2(io_service);
dir_monitor2.add_directory(dir_name2);
dir_monitor2.async_monitor(&handler_B);
// ... Add file to dir_name2.
{
// Use scope to enforce lifetime.
boost::asio::dir_monitor dir_monitor3(io_service);
dir_monitor3.add_directory(dir_name3);
dir_monitor3.async_monitor(&handler_C);
}
io_service.run();
尽管与handler_B()
(成功)和handler_C()
(中止)相关联的操作不会被阻止,但basic_dir_monitor_service
中的单个线程被阻止,等待对dir_name1
的更改。
- C++映射:具有自定义类的运算符[]不起作用(总是返回0)
- 如何将点击的信号和插槽添加到qt中的自定义按钮中
- C++自定义比较函数
- 如何比较自定义类的std::变体
- std::设置自定义比较器
- 如何正确实现和访问运算符的各种自定义枚举器
- flutter:即使shouldRepaint()返回true,自定义画家也不会重新绘制
- 自定义先决条件对移动分配运算符有效吗
- 使用VS Code和CMake Tools运行自定义命令
- 如何创建从Maya(或类似程序)到虚幻引擎的自定义数据导出插件
- std::ranges::elements_view,用于自定义类似元组的数据
- 跟随整数索引列表的自定义类迭代器
- 参数化自定义CMake工具链
- Movesense设备中的真实自定义Gatt服务和特性
- boost asio自定义分配器处理程序io服务编译后错误
- 向多个自定义c++ BLE GATT服务添加特征用户描述
- 向自定义c++ BLE GATT服务添加特征用户描述
- 试图理解Boost.Asio自定义服务实现
- 自定义web服务中的Boost::asio::read()阻塞
- 如何使用自定义绑定从非托管c++连接到WCF服务