【问题标题】:Boost (Beast) websocket: synchronous write hangsBoost (Beast) websocket:同步写入挂起
【发布时间】:2019-02-27 11:07:26
【问题描述】:

我遇到了 boost beast websocket 流的问题。当我尝试写入远程端点已停止响应(特别是由于远程与网络物理断开连接)的流时,此问题会间歇性发生。

发生此问题时,同步 stream.write() 调用最终会挂起很长时间(几分钟),直到套接字最终关闭。我知道这种行为很可能是因为我的程序继续写入流而没有来自远程的确认,直到发送缓冲区已满。我想知道是否有一种方法可以对 write 调用应用超时,或者是否有一个更类似于 stream.try_write() 的接口,我可以将错误处理提升到用户级别。

我确实意识到一种选择是使用 async_write 接口。但是,我担心这会将套接字写入操作推迟到 io_context 的下一次迭代,从而对我的发送性能产生负面影响。

下面是调用挂起时线程的堆栈跟踪。

#0  0x00007f468cf33624 in poll () from /lib64/libc.so.6
#1  0x000000000043e5a7 in boost::asio::detail::socket_ops::poll_write (ec=..., msec=-1, state=0 '\000', s=16)
    at /usr/include/boost/asio/detail/impl/socket_ops.ipp:1898
#2  boost::asio::detail::socket_ops::sync_send (ec=..., all_empty=<optimized out>, flags=0, count=<optimized out>, bufs=0x7fff43c17e20, 
    state=<optimized out>, s=<optimized out>) at /usr/include/boost/asio/detail/impl/socket_ops.ipp:1224
#3  boost::asio::detail::reactive_socket_service_base::send<boost::asio::detail::prepared_buffers<boost::asio::const_buffer, 64ul> > (impl=..., 
    buffers=..., ec=..., this=<optimized out>, flags=0) at /usr/include/boost/asio/detail/reactive_socket_service_base.hpp:245
#4  0x0000000000481c71 in boost::asio::basic_stream_socket<boost::asio::ip::tcp>::write_some<boost::asio::detail::prepared_buffers<boost::asio::const_buffer, 64ul> > (ec=..., buffers=..., this=0x108ad50) at /usr/include/boost/asio/buffer.hpp:941
#5  boost::asio::detail::write_buffer_sequence<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::beast::buffers_cat_view<boost::asio::mutable_buffer, boost::beast::buffers_prefix_view<boost::beast::buffers_suffix<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> > >, boost::beast::buffers_cat_view<boost::asio::mutable_buffer, boost::beast::buffers_prefix_view<boost::beast::buffers_suffix<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> > >::const_iterator, boost::asio::detail::transfer_all_t> (completion_condition=..., ec=..., buffers=..., s=...)
    at /usr/include/boost/asio/impl/write.hpp:53
#6  boost::asio::write<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::beast::buffers_cat_view<boost::asio::mutable_buffer, boost::beast::buffers_prefix_view<boost::beast::buffers_suffix<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> > >, boost::asio::detail::transfer_all_t> (ec=..., buffers=..., s=..., completion_condition=...) at /usr/include/boost/asio/impl/write.hpp:69
#7  boost::asio::write<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::beast::buffers_cat_view<boost::asio::mutable_buffer, boost::beast::buffers_prefix_view<boost::beast::buffers_suffix<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> > > > (ec=..., buffers=..., s=...)
    at /usr/include/boost/asio/impl/write.hpp:92
#8  boost::beast::websocket::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >::write_some<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> (this=this@entry=0x108ad50, fin=fin@entry=true, buffers=..., ec=...) at /usr/include/boost/beast/websocket/impl/write.ipp:625
#9  0x000000000042c5e1 in boost::beast::websocket::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >::write<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> (ec=..., buffers=..., this=0x108ad50)

【问题讨论】:

  • 如果你只是run()io_context,那么显然不会有更多的延迟。此外,异步执行是一个更好的扩展建议,因此除非您打算将 IO“零”延迟到单个端点,否则它可能会执行得更好
  • 我的目标是延迟,我的带宽要求很低。我在 io_context::run() 的上下文中,但是在提议的 async_write() 调用和服务的下一次迭代之间可以调用许多其他回调。
  • 那么就不要在同一个服务上运行所有那些“许多”其他的东西。据我观察,一些服务甚至会立即执行它们的操作(至少 UDP async_send_to 确实会立即生效,即使没有运行 io_context)
  • 使用 async_send 还有其他含义,例如必须管理发送缓冲区的内存,而不是在堆栈缓冲区上调用 write()。如果同步 write() 是 boost 想要支持的东西,那么这个问题应该有解决方案,因为它会导致接口无法使用。
  • 能够解决此问题,请参阅我刚刚发布的答案。

标签: c++ boost boost-asio boost-beast


【解决方案1】:

Beast websockets 不支持非阻塞模式。如果您在与 websocket 流一起使用的套接字上设置此模式,websocket::stream 的实现将在某些情况下产生未定义的行为。缺少超时是同步代码的普遍问题。除了使用异步操作之外,您真的别无选择。你说你想直接从堆栈中发送缓冲区,这很容易在异步上下文中通过使用协程来完成(参见boost::asio::spawnboost::asio::yield_context)。

有很多技术可以让异步 I/O 执行与同步 I/O 相同或更好的性能,包括在所需延迟较低的情况下。

这是 Boost.Asio 的作者直接提供的关于实现超低延迟的建议:https://groups.google.com/a/isocpp.org/d/msg/sg14/FoLFHXqZSck/i4rdO-O3BQAJ

【讨论】:

  • 感谢 vinnie 的跟进,这帮助我解决了问题。
  • 出于好奇,您能否详细说明为什么不支持非阻塞套接字?
  • 是的。 Beast websocket 操作可以涉及读取和写入。例如,在执行读取时,实现将自动响应 ping 并关闭帧。这意味着当您执行读取时,实现可能还需要为您执行写入。您仍然可以自己进行写入,但这些用户发起的写入会与 beast 执行的其他写入一起安排。由于这些原因,支持非阻塞模式是不切实际的。
【解决方案2】:

我能够通过将底层流套接字置于非阻塞模式来解决这个问题:

socket.non_blocking(true);

在此模式下,一旦发送缓冲区已满,write() 调用将立即返回 boost::system::error_code::try_again(也称为 posix EAGAIN)。

【讨论】:

    猜你喜欢
    • 2018-10-06
    • 2018-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多