请考虑以下代码:
void TCP::sendMessage(const std::string& msg) {
std::ostream os{&m_send_streambuf};
os << msg;
if (not m_pending_send) {
doSend();
}
}
void TCP::doSend() {
m_pending_send = true;
boost::asio::async_write( m_socket, m_send_streambuf
, [this](boost::system::error_code ec, std::size_t len)
{
if (ec) {
throw std::runtime_error(ec.message());
}
m_send_streambuf.consume(len);
if (m_send_streambuf.size() >= 1) {
// There's still some data to send
doSend();
}
else {
m_pending_send = false;
}
});
}
m_send_streambuf
是boost::asio::streambuf
,m_pending_send
表示异步写入待处理。
我不确定使用这样的streambuf
是否是个好主意,因为调用sendMessage
会修改缓冲区,而异步写入可能正在运行。
那么,使用这样的streambuf
是否安全?或者也许我应该使用某种双缓冲?
答案 0 :(得分:4)
The documentation of async_write关于缓冲区参数状态:
包含要写入的数据的一个或多个缓冲区。尽管可以根据需要复制buffers对象,但调用者仍保留底层内存块的所有权,这必须保证它们在调用处理程序之前保持有效。
因此,您不应对流缓冲区执行任何其他写操作,因为这可能会导致传递给async_write
的基础内存块无效。
你可以做的只是使用stringstream
缓冲传入的数据,一旦挂起的异步写入完成,将内容刷新到boost流缓冲区并调度新的异步写入。当然,您可以使用更复杂的机制,例如维护流缓冲池以保存其状态信息(待处理或可用),并在每次调用sendMessage
时从池中获取新的流缓冲区,并调度新的异步写入。这完全取决于您的要求 - 吞吐量和延迟或内存消耗。
答案 1 :(得分:1)
这不安全。
async_write()
操作由对stream.async_write_some()
的零次或多次调用组成,这要求提供的缓冲区的底层内存块在调用完成处理程序之前保持有效:
[...]调用者保留了底层内存块的所有权,这必须保证它们在调用处理程序之前保持有效。
async_write()
操作将通过basic_streambuf::data()
获得表示basic_streambuf
输入序列的缓冲区。写入basic_streambuf
,例如通过std::ostream
,可以使缓冲区无效,如文档中所述:
返回的对象由修改输入序列或输出序列的任何
basic_streambuf
成员函数无效。
在写入操作未决时缓冲要写入的数据的一个优雅解决方案是使用队列,如Sam的回答here中所示。
直接向操作提供basic_streambuf
时,操作本身将处理调用consume()
或commit()
。如果直接向操作提供consume()
的缓冲区,则只需要显式调用commit()
或basic_streambuf
。
因此,在发布的代码中,正在发生双consume()
,这可能导致在将m_send_streambuf
的输入序列写入套接字之前将其写入// The streambuf is provided directly to the operation, so the write
// operation will `consume()` the written data.
boost::asio::async_write(..., m_send_streambuf,
[](boost::system::error_code ec, std::size_t len)
{
// Boost.Asio has already performed m_send_streambuf.consume(len) on
// behalf of the user, as the streambuf was directly provided.
...
// Data that is in the input sequence but has not yet been written
// to the socket will be consumed.
m_send_streambuf.consume(len);
});
的输入序列:
consume()
删除显式data()
或将输入序列(Data$TMM
[1] 720 540 400 540 360 720 360
> Data$Date
[1] "2015-09-24" "2015-09-26" "2015-10-01" "2015-10-03" "2015-10-09" "2015-10-10"
[7] "2015-10-11"
)传递给写操作而不是streambuf。有关streambuf的更多详细信息,请考虑阅读this answer。