【问题标题】:Boost.Asio async_send questionBoost.Asio async_send 问题
【发布时间】:2011-03-17 20:25:23
【问题描述】:

我正在将 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)
}

但现在我想知道如果我有多个客户端,是否需要为每个连接创建一个单独的向量?

或者我可以使用那个单一的向量吗?如果我能够使用该单个向量,如果我覆盖其中的内容,是否会弄乱我发送给所有客户的数据?

【问题讨论】:

    标签: c++ boost-asio


    【解决方案1】:

    一个可能的解决方法是使用shared_ptr 来保存您的本地vector 并更改处理程序的签名以接收shared_ptr 以延长data 的寿命直到发送完成(感谢蒂姆向我指出了这一点):

    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));
    }
    

    【讨论】:

    • @Marlon:与任何与性能相关的事情一样:这取决于,但它与涉及shared_ptrs(例如@Tim's)的任何其他解决方案一样有效。
    • @Tim Sylvester - 不明白为什么 shared_ptr 应该在函数退出时删除数据 - boost::bind() 仍然有 的副本。它与您的解决方案不同,但我们没有传递给 boost::bind 的 shared_ptr,而是传递了额外的数据,其中 shared_ptr<...>?
    • @dimba:在最初的版本中,它按照蒂姆所说的做了。
    • @Eugen Constantin Dinca - 所以现在你的答案包含工作代码? :)
    【解决方案2】:

    我通过将shared_ptr 传递给处理函数的数据解决了类似的问题。由于 asio 在被调用之前一直保留处理程序函子,并且处理程序函子保留 shared_ptr 引用,因此只要有打开的请求,数据就会保持分配状态。

    编辑 - 这是一些代码:

    这里连接对象持有当前正在写入的数据缓冲区,所以shared_ptr是连接对象,bind调用将方法函子附加到对象引用,asio调用保持对象存活.

    关键是每个处理程序必须使用另一个引用启动一个新的异步操作,否则连接将被关闭。一旦连接完成或发生错误,我们就会停止生成新的读/写请求。需要注意的是,您需要确保检查所有回调中的错误对象。

    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)
    {
    

    【讨论】:

    • 不确定这是对 OP 问题的回答。 OP 描述的恕我直言,当您的处理程序有多个并发(尚未完成)async_send 时。在这种情况下,您的处理程序应该为每个并发异步操作拥有相同数量的缓冲区。
    • @dimba OP 询问从基于临时堆栈的状态转移到全局状态。他的两个例子都有一个缓冲区,这是不够的。将状态保持在异步回调中可以消除局部状态被破坏的问题,而不会增加需要显式管理全局状态的开销,例如,在受互斥体保护的向量向量中。
    【解决方案3】:

    但现在我想知道我是否有 多个客户,我需要 为每个创建一个单独的向量 连接?

    是的,尽管每个向量不需要在全局范围内。此问题的典型解决方案是将buffer 保留为对象的成员,并将该对象的成员函数绑定到传递给async_write 完成处理程序的函子。这样,缓冲区将在异步写入的整个生命周期内保留在范围内。 asio examples 到处都是使用thisshared_from_this 绑定成员函数的用法。一般来说,最好使用shared_from_this 来简化对象的生命周期,尤其是io_service:stop()~io_service() 中的in the face。尽管对于简单的示例,这种脚手架通常是不必要的。

    描述的销毁顺序 以上允许程序简化 他们的资源管理通过使用 shared_ptr。一个物体在哪里 生命周期与 a 的生命周期相关联 连接(或其他一些序列 异步操作),一个 shared_ptr 到对象将被绑定到 所有异步的处理程序 与之相关的操作。

    async echo server 是一个很好的起点,因为它很简单。

    boost::asio::async_write(
        socket,
        boost::asio::buffer(data, bytes_transferred),
        boost::bind(
            &session::handle_write,
            this,
            boost::asio::placeholders::error
        )
    );
    

    【讨论】:

      【解决方案4】:

      我一直这样做的方式是真正将“TCP 是一个流”的概念牢记在心。所以我为每个连接都有一个boost::asio::streambuf 来代表我发送给客户端的内容。

      与 boost 中的大多数示例一样,我有一个 tcp_connection 类,每个连接都有一个对象。每个人都有一个成员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));
      

      【讨论】:

        【解决方案5】:

        除非您向所有客户端发送相同且恒定的数据(如提示消息),否则您不能使用单个向量。这是由异步 I/O 的性质引起的。如果您正在发送,系统将在其队列中保留指向您的缓冲区的指针以及一些 AIO 数据包结构。一旦它完成了一些先前的排队发送操作并且它自己的缓冲区中有空闲空间,系统将开始为您的数据形成数据包,并在 TCP 帧中的相应位置复制您的缓冲区块。因此,如果您在此过程中修改缓冲区的内容,您将破坏发送到客户端的数据。如果您正在接收,系统可能会进一步优化它并将您的缓冲区作为 DMA 操作的目标提供给 NIC。在这种情况下,数据复制可以节省大量 CPU 周期,因为它是由 DMA 控制器完成的。不过,这种优化可能只有在 NIC 支持硬件 TCP 卸载时才会起作用。

        更新:在 Windows 上,Boost.Asio 使用 overlapped WSA IO 并通过 IOCP 发出完成通知。

        【讨论】:

          【解决方案6】:

          Krit 解释了数据损坏,所以我会给你一个实施建议。

          我建议您为当前正在执行的每个发送操作使用单独的向量。您可能不希望每个连接都有一个,因为您可能希望在同一连接上按顺序发送多条消息,而无需等待前一个消息完成。

          【讨论】:

          • 您不能在 TCP 上同时发送数据,否则您的数据将被交错和损坏。这真的只对 UDP 消息有意义。
          • 如果“同时”Tobbe 意味着 async_send 调用是按顺序完成的,而不是等待前面的完成,这实际上是完全合理的。系统保证数据将按照提交操作的顺序发送。此外,始终将一些数据保留在系统的“发送”队列中以实现称为“延迟 ACK”的 TCP 级优化是有益的。这在快速网络上尤其明显。
          • 克里特,我就是这个意思。谢谢你为我解释:)
          • Krit:我偷了你的公式来改进答案。希望你没问题。谢谢!
          【解决方案7】:

          每个连接需要一个写入缓冲区,其他人一直在说每个连接使用一个向量,这是您最初的想法,但为了简单起见,我建议您在新方法中使用字符串向量。

          Boost.ASIO 有一些特殊情况,围绕使用字符串及其缓冲区进行写入,这使得它们更易于使用。

          只是一个想法。

          【讨论】:

            猜你喜欢
            • 2013-06-03
            • 2019-03-22
            • 1970-01-01
            • 2016-12-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-01-10
            • 2012-08-13
            相关资源
            最近更新 更多