为什么boost::asio::async_write第一次调用正常,第二次调用出错?
第一次调用async_write可以成功通过TCP发送数据,但第二次调用async_write时就出现异常。如下是完整的复现代码:
#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
我找到一个可行的变通方案可以让程序正常运行,相关改动如下:
auto to_send_data = send_data; asio::async_write(socket1, asio::dynamic_buffer(to_send_data),
请问原来的代码出错的根本原因是什么?
补充更新
- 我尝试在VSCode IDE中给
std::vector::resize的实现打断点(测试环境为Ubuntu),断点无效显示为灰色,我确认程序是Debug模式编译的。使用GDB设置断点也提示"Function "std::vector::resize" not defined."。 - 我给相关
operator()的实现打断点后发现,default分支从未被触发,也就是说start的值始终为1。
问题解答
你出错的核心原因是boost::asio::dynamic_buffer的消耗式设计:
当你给async_write传入asio::dynamic_buffer(send_data)时,async_write操作完成后,dynamic_buffer会自动调用send_data.erase(0, n)移除已经成功发送的前n个字节。第一次调用async_write成功发送512000字节后,send_data的长度就被清空为0,第二次再调用async_write传入同一个send_data的dynamic_buffer时,缓冲区已经没有可发送的内容,自然就返回发送了0字节的结果。
而你修改后的方案每次都先拷贝得到新的to_send_data临时对象,每次传入dynamic_buffer的都是全新的、长度为512k的vector,操作完成后临时对象直接销毁,不会影响下一次循环的发送缓冲区,所以可以正常运行。
针对你补充的断点问题:std::vector是模板类,成员函数默认会被内联优化,就算开启Debug模式,部分编译器也不会生成独立的resize函数符号,所以GDB和VSCode找不到对应的函数断点。你可以把断点打在async_write的回调函数里,打印send_data.size()就能验证上述结论,第一次回调后send_data.size()的值就是0。
内容的提问来源于stack exchange,提问作者John

