[英]Boost.Asio async_send question
我正在使用Boost.Asio来编写我正在编写的服务器应用程序。
async_send
要求调用者保持正在发送的数据的所有权,直到数据成功发送。 这意味着我的代码(看起来如下所示)将失败,并且确实如此,因为data
将不再是有效对象。
void func()
{
std::vector<unsigned char> data;
// ...
// fill data with stuff
// ...
socket.async_send(boost::asio::buffer(data), handler);
}
所以我的解决方案是做这样的事情:
std::vector<unsigned char> data;
void func()
{
// ...
// fill data with stuff
// ...
socket.async_send(boost::asio::buffer(data), handler)
}
但现在我想知道我是否有多个客户端,我是否需要为每个连接创建一个单独的向量?
或者我可以使用那个单一的向量? 如果我能够使用那个单一的向量,如果我覆盖其中的内容会弄乱我发送给我所有客户端的数据吗?
一个可能的解决方法是使用shared_ptr
保存本地vector
并更改处理程序的签名以接收shared_ptr
以便延长data
的生命周期,直到发送完成(感谢Tim向我指出):
void handler( boost::shared_ptr<std::vector<char> > data )
{
}
void func()
{
boost::shared_ptr<std::vector<char> > data(new std::vector<char>);
// ...
// fill data with stuff
// ...
socket.async_send(boost::asio::buffer(*data), boost:bind(handler,data));
}
我通过将shared_ptr
传递给我的数据到处理函数来解决了类似的问题。 由于asio在调用之前保留了处理程序仿函数,并且hander仿函数保留了shared_ptr
引用,因此只要存在打开的请求,数据就会保持分配状态。
编辑 - 这是一些代码:
这里连接对象保持正在写入的当前数据缓冲区,因此shared_ptr
指向连接对象, bind
调用将方法仿函数附加到对象引用,而asio调用使对象保持活动状态。
关键是每个处理程序必须使用另一个引用启动新的asyc操作,否则连接将被关闭。 一旦完成连接或发生错误,我们就会停止生成新的读/写请求。 需要注意的是,您需要确保在所有回调中检查错误对象。
boost::asio::async_write(
mSocket,
buffers,
mHandlerStrand.wrap(
boost::bind(
&TCPConnection::InternalHandleAsyncWrite,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
void TCPConnection::InternalHandleAsyncWrite(
const boost::system::error_code& e,
std::size_t bytes_transferred)
{
但现在我想知道我是否有多个客户端,我是否需要为每个连接创建一个单独的向量?
是的,尽管每个向量不需要在全局范围内。 此问题的典型解决方案是将buffer
保留为对象的成员,并将该对象的成员函数绑定到传递给async_write
完成处理程序的async_write
函数。 这样,缓冲区将在异步写入的整个生命周期内保留在范围内。 asio 示例充斥着使用this
和shared_from_this
的绑定成员函数的这种用法。 通常,最好使用shared_from_this
来简化对象的生命周期,特别是面对 io_service:stop()
和~io_service()
。 虽然对于简单的例子,这种脚手架通常是不必要的。
上述销毁序列允许程序通过使用shared_ptr <>来简化其资源管理。 如果对象的生命周期与连接的生命周期(或其他一些异步操作序列)相关联,则对象的shared_ptr将绑定到与其关联的所有异步操作的处理程序中。
一个好的起点是异步回显服务器,因为它简单。
boost::asio::async_write(
socket,
boost::asio::buffer(data, bytes_transferred),
boost::bind(
&session::handle_write,
this,
boost::asio::placeholders::error
)
);
我一直在做的方式是真正把“TCP是一个流”的概念铭记于心。 所以我为每个连接都有一个boost::asio::streambuf
来表示我发送给客户端的内容。
像boost中的大多数示例一样,我有一个tcp_connection
类,每个连接都有一个对象。 每个人都有一个memeber boost::asio::streambuf response_;
当我想向客户发送内容时,我就这样做:
std::ostream responce_stream(&response_);
responce_stream << "whatever my responce message happens to be!\r\n";
boost::asio::async_write(
socket_,
response_,
boost::bind(
&tcp_connection::handle_write,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
除非向所有客户端发送相同且常量的数据(如提示消息),否则不能使用单个向量。 这是由异步I / O的性质引起的。 如果您要发送,系统将在其队列中保留指向缓冲区的指针以及一些AIO数据包结构。 一旦完成一些先前排队的发送操作并且在其自己的缓冲区中有一个空闲空间,系统将开始为您的数据形成数据包,并在TCP帧中的相应位置内复制缓冲区块。 因此,如果您沿途修改缓冲区的内容,则会破坏发送给客户端的数据。 如果您正在接收,系统可能会进一步优化它并将缓冲区作为DMA操作的目标提供给NIC。 在这种情况下,可以在数据复制时节省大量的CPU周期,因为它是由DMA控制器完成的。 但是,可能只有当NIC支持硬件TCP卸载时,此优化才有效。
Krit解释了数据损坏,所以我会给你一个实现建议。
我建议你为当前正在执行的每个发送操作使用一个单独的向量。 您可能不希望每个连接都有一个,因为您可能希望在同一连接上顺序发送多条消息,而无需等待先前的连接完成。
每个连接需要一个写缓冲区,其他人一直在说每个连接使用一个向量,就像你最初的想法一样,但我建议为了简单起见,使用新方法的字符串向量。
Boost.ASIO有一些特殊情况,它们使用字符串及其缓冲区进行写入,这使得它们更易于使用。
只是一个想法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.