我正在将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)
}
但现在我想知道我是否有多个客户端,是否需要为每个连接创建一个单独的向量?
或者我可以使用那个单一的载体吗?如果我能够使用那个单一的向量,如果我覆盖其中的内容会弄乱我发送给所有客户的数据吗?
答案 0 :(得分:13)
可能的解决方法是使用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));
}
答案 1 :(得分:6)
我通过将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)
{
答案 2 :(得分:5)
但现在我想知道我有没有 我需要多个客户 为每个人创建一个单独的向量 连接?
是的,尽管每个向量不需要在全局范围内。此问题的典型解决方案是将buffer
保留为对象的成员,并将该对象的成员函数绑定到传递给async_write
完成处理程序的仿函数。这样,缓冲区将在异步写入的整个生命周期内保留在范围内。 asio examples充斥着使用this
和shared_from_this
的绑定成员函数的这种用法。通常,最好使用shared_from_this
来简化对象的生命周期,尤其是io_service:stop()
和~io_service()
的{{3}}。虽然对于简单的例子,这种脚手架通常是不必要的。
描述的破坏序列 以上允许程序简化 他们的资源管理使用 的shared_ptr&LT;取代。对象的位置 寿命与a的寿命有关 连接(或其他一些序列) 异步操作),一个shared_ptr 将对象绑定到 所有异步处理程序 与之相关的操作。
由于其简单性,in the face是一个很好的起点。
boost::asio::async_write(
socket,
boost::asio::buffer(data, bytes_transferred),
boost::bind(
&session::handle_write,
this,
boost::asio::placeholders::error
)
);
答案 3 :(得分:3)
我一直在做的方式是真正把“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));
答案 4 :(得分:2)
除非向所有客户端发送相同且常量的数据(如提示消息),否则不能使用单个向量。这是由异步I / O的性质引起的。如果您要发送,系统将在其队列中保留指向缓冲区的指针以及一些AIO数据包结构。一旦完成一些先前排队的发送操作并且在其自己的缓冲区中有一个空闲空间,系统将开始为您的数据形成数据包,并在TCP帧中的相应位置内复制缓冲区块。因此,如果您沿途修改缓冲区的内容,则会破坏发送给客户端的数据。如果您正在接收,系统可能会进一步优化它并将缓冲区作为DMA操作的目标提供给NIC。在这种情况下,可以在数据复制时节省大量的CPU周期,因为它是由DMA控制器完成的。但是,可能只有当NIC支持硬件TCP卸载时,此优化才有效。
更新:在Windows上,Boost.Asio使用overlapped WSA IO通过IOCP完成通知。
答案 5 :(得分:2)
Krit解释了数据损坏,所以我会给你一个实现建议。
我建议您为当前正在执行的每个发送操作使用单独的向量。您可能不希望每个连接都有一个,因为您可能希望在同一连接上顺序发送多条消息,而无需等待先前的连接完成。
答案 6 :(得分:0)
每个连接需要一个写缓冲区,其他人一直在说按原始想法使用每个连接的向量,但我建议为了简单起见,使用新方法的字符串向量。
Boost.ASIO有一些特殊情况,它们使用字符串及其缓冲区进行写入,这使得它们更易于使用。
只是一个想法。