使用Boost::asio async_read时程序崩溃及超时问题求助
Boost.Asio TCP通信async_read崩溃及超时逻辑异常问题
问题背景
基于Boost::asio开发TCP服务器,服务器与telnet客户端连接成功后,async_write可正常向客户端发送数据,但调用async_read读取客户端数据时程序崩溃,推测问题出在缓冲区但无法定位。后续发现readResponse函数未等待10秒超时就直接抛出超时异常,测试确认计时器触发但IO回调被阻塞,函数直接跳转到条件判断返回。代码由之前的串口通信项目修改而来,串口通信场景下无此问题。
相关代码
session::readResponse函数
std::vector<char> session::readResponse(const int move_id) { int data_size; switch (move_id) { // receive data from info1 case 1: data_size = 20; break; // receive data from info2 case 2: data_size = 19; break; default: data_size = 0; break; } auto self(shared_from_this()); bool data_available = false; std::vector<char> char_vector(data_size); try { boost::mutex::scoped_lock scoped_locker(*mutex_); boost::asio::async_read( socket_, boost::asio::buffer(char_vector, data_size), [&](const boost::system::error_code &error, std::size_t bytes_transferred) { if (error) { data_available = false; std::cerr << ">>> readCallback Error " << error << std::endl; } timeout_->cancel(); data_available = true; }); timeout_->expires_from_now(boost::posix_time::seconds(10)); timeout_->async_wait( [&](const boost::system::error_code &error) { if (!error) { data_available = false; socket_.cancel(); std::cerr << ">>> Read timeout." << std::endl; } }); } catch(const std::exception& e) { std::cerr << ">>> Read exception. " << e.what() << '\n'; } if (data_available) { return char_vector; } else { throw ">>> reading timeout"; } }
gManualSendCommand函数
void gManualSendCommand(Server& srv) { while( true ) { if(srv.getSession() == NULL) { } else { std::cout << "Enter robot command id: "; std::string id; std::getline(std::cin, id); srv.getSession()->writeCommand(std::stoi(id)); srv.getSession()->readResponse(std::stoi(id)); std::cout << "\n"; } } }
问题分析与修复方案
核心问题
- 异步操作同步化错误:
readResponse试图用异步的async_read/async_wait实现同步读取,但Boost.Asio异步操作是非阻塞的,函数发起异步调用后会直接执行到末尾的条件判断,此时data_available尚未被回调修改,导致直接抛出超时异常。 - 局部变量悬空引用:两个lambda回调通过
[&]捕获局部变量(data_available、socket_等),但readResponse执行完毕后局部变量会被销毁,回调再访问这些引用会触发未定义行为,这是程序崩溃的根本原因。
修复方案
方案1:改用同步读取+超时(简单直接)
若无需异步并发优势,可使用同步read结合定时器实现超时:
std::vector<char> session::readResponse(const int move_id) { int data_size; switch (move_id) { case 1: data_size = 20; break; case 2: data_size = 19; break; default: data_size = 0; break; } std::vector<char> char_vector(data_size); boost::system::error_code ec; // 设置超时定时器 timeout_->expires_from_now(boost::posix_time::seconds(10)); timeout_->async_wait([this](const boost::system::error_code& error) { if (!error) socket_.cancel(); }); // 同步读取 boost::asio::read(socket_, boost::asio::buffer(char_vector, data_size), ec); // 取消定时器 timeout_->cancel(); if (ec == boost::asio::error::operation_aborted) { throw ">>> reading timeout"; } else if (ec) { std::cerr << ">>> Read Error " << ec << std::endl; throw ">>> read failed"; } return char_vector; }
方案2:正确使用异步模型(符合Asio设计理念)
若需保留异步操作,需将读取逻辑改为异步回调链式调用,避免局部变量悬空:
// 修改为异步接口,通过回调返回结果 void session::readResponse(const int move_id, std::function<void(std::vector<char>, bool)> callback) { int data_size; switch (move_id) { case 1: data_size = 20; break; case 2: data_size = 19; break; default: data_size = 0; break; } auto self = shared_from_this(); auto buffer = std::make_shared<std::vector<char>>(data_size); timeout_->expires_from_now(boost::posix_time::seconds(10)); timeout_->async_wait([self, callback](const boost::system::error_code& error) { if (!error) { self->socket_.cancel(); std::cerr << ">>> Read timeout." << std::endl; callback({}, false); } }); boost::asio::async_read(self->socket_, boost::asio::buffer(*buffer, data_size), [self, buffer, callback](const boost::system::error_code& error, std::size_t bytes_transferred) { self->timeout_->cancel(); if (error) { std::cerr << ">>> readCallback Error " << error << std::endl; callback({}, false); return; } callback(std::move(*buffer), true); }); }
同时修改调用逻辑:
void gManualSendCommand(Server& srv) { while( true ) { if(srv.getSession() == NULL) { std::this_thread::sleep_for(std::chrono::seconds(1)); continue; } std::cout << "Enter robot command id: "; std::string id; std::getline(std::cin, id); int cmd_id = std::stoi(id); srv.getSession()->writeCommand(cmd_id); // 异步回调处理读取结果 srv.getSession()->readResponse(cmd_id, [](std::vector<char> data, bool success) { if (success) { std::cout << "Read data: " << std::string(data.begin(), data.end()) << std::endl; } else { std::cerr << "Read failed or timeout" << std::endl; } }); std::cout << "\n"; } }
额外注意事项
- 确保
session对象通过shared_ptr管理(已使用shared_from_this(),符合要求),避免回调触发时对象已销毁。 - 同步读取场景下需保证线程安全,避免同一socket被多线程同时操作。
- 串口通信因模型差异(如默认阻塞读取)未暴露问题,但TCP异步模型需严格遵循Asio异步编程范式。
内容的提问来源于stack exchange,提问作者RyanChen.YLC
相关产品推荐
相关产品推荐

