为什么 boost::asio::async_write 第一次很好,第二次就出错了?

Why boost::asio::async_write works well at the first time and something goes wrong for the second time?

我一开始调用async_write可以成功通过TCP发送数据,但是再次调用async_write就出错了。 这是 code snippet:

#include <algorithm>
#include <array>
#include <boost/asio.hpp>
#include <boost/range.hpp>
#include <chrono>
#include <iostream>
#include <random>
#include <vector>

const std::size_t buf_size = 500*1024;
const int test_cycles = 1.024e5;

namespace asio = boost::asio;

int main()
{
  std::vector<char> send_data(buf_size);

  std::vector<char> recv_buf(buf_size);

  asio::io_service ios;

  asio::ip::tcp::socket socket1(ios);
  asio::ip::tcp::socket socket2(ios);
  asio::ip::tcp::acceptor acceptor(ios, {asio::ip::tcp::v4(), 55557});
  socket1.connect({asio::ip::address_v4::loopback(), 55557});
  acceptor.accept(socket2);

  for (std::size_t i = 0; i < 1; ++i)
  {
      auto start = std::chrono::steady_clock::now();
      for(int j=0; j < test_cycles; ++j)
      {
            size_t written_bytes = 0;
            auto to_send_data = send_data;
            asio::async_write(socket1,
                asio::dynamic_buffer(send_data),
                [&](auto ec, auto n)
                {
                    if(!ec)
                    {
                        std::cout << "successfully sent " << n << std::endl;
                    }
                    else
                    {
                        std::cout << ec.message() << std::endl;
                    }

                    if(0==n)
                    {
                        std::cout << "send error" << std::endl;
                    }

                    written_bytes = n;
                });

            asio::async_read(socket2, asio::buffer(recv_buf),
                [&](auto ec, auto n)
                {
                    if(!ec)
                    {
                        //std::cout << "received " << n << std::endl;
                    }
                    else
                    {
                        std::cout << ec.message() << std::endl;
                    }

                    if(0==n)
                    {
                        std::cout << "received error" << std::endl;
                    }

                    if(written_bytes != n)
                    {
                        std::cout << "received is not same with the sent" << std::endl;
                    }
                });
                
                ios.run();
                ios.reset();
        }
        
        auto end = std::chrono::steady_clock::now();
        std::chrono::duration<float> elapsed = end - start;
        std::cout << elapsed.count() << " seconds\n";
        std::cout << (buf_size * test_cycles / elapsed.count() / 1024 / 1024/ 1024) << " GB/s\n";
  }
}

这是输出:

successfully sent 512000
successfully sent 0
send error

一些提示

我找到了一个 解决方法 程序运行 well.Here 是相关的代码片段:

        auto to_send_data = send_data;
        asio::async_write(socket1,
            asio::dynamic_buffer(to_send_data),

为什么上述代码片段会出错?

更新:

  1. 我尝试通过 VsCode IDE 在 STD::vector::resize() 的实现中设置断点(我在 Ubuntu。),但断点确实不起作用(即断点是灰色的。)。我可以保证二进制程序是作为调试模式构建的。我也尝试通过 GDB 设置断点,但 GDB 输出“函数“std::vector::resize”未定义。”

  2. 我在上述operator()的实现中设置了断点,我发现default never确实触发了,换句话说,开始总是1.

不幸的是boost documentation on dynamic_buffer非常简洁:

A dynamic buffer encapsulates memory storage that may be automatically resized as required.

意味着dynamic_buffer将在IO操作期间操纵底层向量。

Boost ASIO tutorial更明确:

Dynamic buffer is a concept. Dynamic buffer is a buffer that you can write data into or read from it. If the buffer isn't big enough to fit your data then it will resize (grow) dynamically. So when you write into the dynamic buffer you don't have to worry if there is enough space left in the buffer. On the other hand, when you read data from a dynamic buffer, you are responsible to throw away (consume) bytes read and no longer needed so the buffer won't grow permanently.

在 boost 调用 consume 的 dynamic_buffer 方法中对动态缓冲区的写入操作从中删除 bytes_tranferred:

class write_dynbuf_v2_op
   ....
   void operator()(const boost::system::error_code& ec,
       std::size_t bytes_transferred, int start = 0)
   {
     switch (start)
    {
      case 1:
         // write operation
         async_write(stream_, buffers_.data(0, buffers_.size()),
               BOOST_ASIO_MOVE_CAST(CompletionCondition)(completion_condition_),
               BOOST_ASIO_MOVE_CAST(write_dynbuf_v2_op)(*this));
        return;
     default:
        // write completed, consume transferred bytes and call handler
        buffers_.consume(bytes_transferred);
        handler_(ec, static_cast<const std::size_t&>(bytes_transferred));
  }
}

因此,在原始示例中,我们将在向量上排队异步写入,然后一次又一次地在 same 向量上排队异步操作。 第一个回调完成后,传输 512000 字节,第二个回调有一个空向量(因为它被 dynamic_buffer 擦除)并且回调打印

Successfully sent 0

因为错误代码是0然后

Send error

当传输的字节数为0时打印send error

io_context.run() 卡住了,因为它有一个挂起的读取操作。

当我们为每个 async_write 操作安排向量副本时,解决方法会有所帮助。 另一种方法是在 io_context.run()

之后将矢量大小调整回原始大小