You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Boost::asio async_read两次调用仅触发一次handler的问题排查咨询

问题分析与解决方案

哥们,你这个问题我之前用Boost.Asio的时候也踩过坑,核心问题出在对io_service::run()的工作逻辑理解上,还有bind参数传递的小错误。咱们一步步拆解来看:

直接导致第二次async_read没触发的原因

当你第一次调用connection->io_service->run()时,它会处理完第一个async_read任务后,因为没有其他待执行的异步操作,run()会直接返回,并且把io_service置为停止状态。这时候你再调用run(),它会直接跳过执行——这就是第二次handler没被触发的关键原因。

另外还有两个隐藏的问题:

  1. 你在bind时传递connection->ec是按值拷贝的,handler里拿到的是原error_code的副本,不会更新你connection对象里的ec值;
  2. 用new分配的Data内存没有释放,会造成内存泄漏。

修正你的现有代码(同步调用异步操作方式)

先把你的代码改成可以正常工作的版本,修复上述问题:

#include <boost/asio.hpp>
#include <iostream>
#include <vector>
#include <boost/bind/bind.hpp>

using namespace std;
using namespace boost::asio;

struct Tcp_connection {
    io_service* io_service;
    ip::tcp::socket* sock;
    boost::system::error_code ec;
};

int calculateSize(uint8_t Header[]) {
    if (Header[2] == 0)
        return Header[3]-4;
    return (Header[2] * 256 + Header[3])-4;
}

void read_handler(
    const boost::system::error_code& ec,
    std::size_t bytes_transferred) {
    if (ec) {
        cout << ec.message() << endl;
    } else {
        cout << "Bytes received " << bytes_transferred << endl;
    }
}

int receive_packet(Tcp_connection* connection) {
    uint8_t Header[4];
    // 用boost::ref传递ec的引用,确保handler能更新原对象的ec
    async_read(*connection->sock, buffer(Header, sizeof(Header)), 
        boost::bind(&read_handler, boost::ref(connection->ec), placeholders::bytes_transferred));
    connection->io_service->run();
    
    // 先检查读报头是否出错
    if (connection->ec) {
        cout << "Read header failed: " << connection->ec.message() << endl;
        return -1;
    }
    
    int dataSize = calculateSize(Header);
    // 用vector管理内存,自动释放避免泄漏
    std::vector<uint8_t> Data(dataSize);
    
    // 重置io_service,让它可以处理新的异步任务
    connection->io_service->reset();
    
    async_read(*connection->sock, buffer(Data), 
        boost::bind(&read_handler, boost::ref(connection->ec), placeholders::bytes_transferred));
    connection->io_service->run();
    
    if (connection->ec) {
        cout << "Read data failed: " << connection->ec.message() << endl;
        return -1;
    }
    
    // 在这里处理Data中的报文数据
    // ...
    
    return 0;
}

推荐的异步编程方式(链式调用)

上面的写法是把异步操作当成同步来用,其实更符合Boost.Asio设计思想的是链式异步调用——在第一个读操作的handler里启动第二个读操作,这样不需要手动resetio_service,代码也更优雅:

#include <boost/asio.hpp>
#include <iostream>
#include <vector>
#include <boost/bind/bind.hpp>
#include <memory>

using namespace std;
using namespace boost::asio;

struct Tcp_connection {
    io_service* io_service;
    ip::tcp::socket* sock;
};

// 用结构体保存异步操作的上下文,避免栈变量提前销毁
struct ReadContext {
    std::shared_ptr<Tcp_connection> connection;
    uint8_t header[4];
    std::vector<uint8_t> data;
};

int calculateSize(uint8_t Header[]) {
    if (Header[2] == 0)
        return Header[3]-4;
    return (Header[2] * 256 + Header[3])-4;
}

// 读数据的handler
void read_data_handler(const boost::system::error_code& ec, std::size_t bytes_transferred, std::shared_ptr<ReadContext> ctx) {
    if (ec) {
        cout << "Read data failed: " << ec.message() << endl;
        return;
    }
    cout << "Received data bytes: " << bytes_transferred << endl;
    
    // 处理接收到的数据
    // ...
}

// 读报头的handler,完成后启动读数据操作
void read_header_handler(const boost::system::error_code& ec, std::size_t bytes_transferred, std::shared_ptr<ReadContext> ctx) {
    if (ec) {
        cout << "Read header failed: " << ec.message() << endl;
        return;
    }
    cout << "Received header bytes: " << bytes_transferred << endl;
    
    int dataSize = calculateSize(ctx->header);
    ctx->data.resize(dataSize);
    
    // 链式启动第二个异步读操作
    async_read(*ctx->connection->sock, buffer(ctx->data),
        boost::bind(&read_data_handler, placeholders::error, placeholders::bytes_transferred, ctx));
}

int receive_packet(std::shared_ptr<Tcp_connection> connection) {
    auto ctx = make_shared<ReadContext>();
    ctx->connection = connection;
    
    // 启动第一个异步读操作
    async_read(*connection->sock, buffer(ctx->header, sizeof(ctx->header)),
        boost::bind(&read_header_handler, placeholders::error, placeholders::bytes_transferred, ctx));
    
    // run会一直运行直到所有异步操作完成
    connection->io_service->run();
    
    return 0;
}

这种方式的好处是:

  1. 不需要手动resetio_service,所有异步操作是链式触发的,io_service会自动处理直到最后一个任务完成;
  2. 用std::shared_ptr管理上下文,避免内存泄漏和栈变量提前销毁的问题;
  3. 更贴合异步编程的非阻塞思想,适合后续扩展到多线程场景。

内容的提问来源于stack exchange,提问作者Serhan Erkovan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 08:28:11