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

Boost Asio类中async_receive_from内定时器无法触发问题

问题:Boost Asio封装类后UDP代理定时器无法触发

尝试用Boost Asio编写小型UDP代理,需求是收到数据报后,在指定时间触发回调转发数据,同时立即准备接收新数据报。非类封装版本可正常运行,定时器能如期触发;但封装为类后,能接收数据报并打印日志,却始终无法触发定时器。

可正常运行的非类版本代码

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

void receive(udp::socket &socket, io_context &ioContext) {
    udp::endpoint receive_endpoint;
    std::array<char, 8192> buf;
    socket.async_receive_from(buffer(buf, buf.size()), receive_endpoint,
                              [&](const boost::system::error_code& error, std::size_t bytesReceived) {
        if (!error && bytesReceived > 0) {
            std::cout << "Got a package" << std::endl;
            boost::asio::steady_timer timer = boost::asio::steady_timer(ioContext, boost::asio::chrono::milliseconds(100));
            timer.async_wait([&](const boost::system::error_code& ec) {
                std::cout << "Timer hit" << std::endl;
            });
            receive(socket, ioContext);
        } else {
            std::cout << "Error receiving data from client" << std::endl;
        }
    });
}

int main() {
    boost::asio::io_context ioContext;
    udp::socket socket(ioContext);
    socket.open(udp::v4());
    udp::endpoint localEndpoint(ip::address::from_string("127.0.0.1"), 12345);
    socket.bind(localEndpoint);
    receive(socket, ioContext);

    ioContext.run();

    return 0;
}

封装为类后的主文件代码

#include "udp_class.h"
#include <iostream>
#include <boost/asio.hpp>

int main() {
    boost::asio::io_context ioContext;
    Proxy my_proxy(ioContext);
    my_proxy.start();
    ioContext.run();

    return 0;
}

类定义(udp_class.h)

#pragma once

#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
#include <iostream>
#include <boost/asio.hpp>
#include <chrono>

using namespace boost::asio;
using ip::udp;
using std::chrono::milliseconds;

class Proxy {
public:
    Proxy(io_context& ioContext): localSocket_(ioContext) { };
    void start() {
        localSocket_.open(udp::v4());
        localSocket_.bind(udp::endpoint(ip::address::from_string("127.0.0.1"), 12345));
        receive();
    };

private:
    void receive() {
        udp::endpoint receive_endpoint;
        std::array<char, 8192> buf;
        localSocket_.async_receive_from(buffer(buf, buf.size()), receive_endpoint,
                [&](const boost::system::error_code& error, std::size_t bytesReceived) {
              if (!error && bytesReceived > 0) {
                  std::cout << "Got a package" << std::endl;
                  boost::asio::steady_timer timer = boost::asio::steady_timer(ioContext, boost::asio::chrono::milliseconds(100));
                  timer.async_wait([&](const boost::system::error_code& ec) {
                      std::cout << "Timer hit" << std::endl;
                  });
                  receive();
              } else {
                  std::cout << "Error receiving data from client" << std::endl;
              }
          });
    }

    io_context ioContext;
    udp::socket localSocket_;
};

Boost Asio调试输出

@asio|1720035449.638484|0*1|socket@0x16b0ef4f8.async_receive_from
@asio|1720035449.638495|.1|non_blocking_recvfrom,ec=system:35,bytes_transferred=0
@asio|1720035451.692791|.1|non_blocking_recvfrom,ec=system:0,bytes_transferred=12
@asio|1720035451.692827|>1|ec=system:0,bytes_transferred=12
@asio|1720035451.692951|1*2|deadline_timer@0x16b0ef038.async_wait
@asio|1720035451.692976|1*3|socket@0x16b0ef4f8.async_receive_from
@asio|1720035451.692987|.3|non_blocking_recvfrom,ec=system:35,bytes_transferred=0
@asio|1720035451.692992|1|deadline_timer@0x16b0ef038.cancel
@asio|1720035451.692998|<1|

问题分析与解决方案

核心错误点

  1. io_context不匹配:类中定义了成员变量io_context ioContext,但构造函数中localSocket_使用的是传入的io_context(main函数中的实例),而定时器却绑定到类自己的ioContext上。main函数中只run了传入的那个io_context,类成员的ioContext从未被调度,因此定时器的异步等待永远不会被执行。
  2. 定时器生命周期过短:定时器是receive函数中的局部变量,当receive函数执行完毕(异步回调返回后),定时器对象被销毁,析构函数会自动cancel所有未完成的异步操作,这也对应了调试输出中的deadline_timer@0x16b0ef038.cancel日志。
  3. Lambda引用捕获风险:回调中使用[&]引用捕获局部变量,可能导致局部变量销毁后引用失效,引发未定义行为。

修改后的类代码

#pragma once

#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
#include <iostream>
#include <boost/asio.hpp>
#include <chrono>
#include <memory>

using namespace boost::asio;
using ip::udp;
using std::chrono::milliseconds;

class Proxy {
public:
    // 保存传入io_context的引用,而非创建新实例
    Proxy(io_context& ioContext): ioContext_(ioContext), localSocket_(ioContext) { };
    void start() {
        localSocket_.open(udp::v4());
        localSocket_.bind(udp::endpoint(ip::address::from_string("127.0.0.1"), 12345));
        receive();
    };

private:
    void receive() {
        udp::endpoint receive_endpoint;
        // 缓冲区用shared_ptr延长生命周期,避免异步操作时失效
        auto buf = std::make_shared<std::array<char, 8192>>();
        localSocket_.async_receive_from(buffer(*buf, buf->size()), receive_endpoint,
            // 捕获this和buf,避免引用失效
            [this, buf](const boost::system::error_code& error, std::size_t bytesReceived) {
                if (!error && bytesReceived > 0) {
                    std::cout << "Got a package" << std::endl;
                    // 用shared_ptr管理定时器,延长生命周期至回调完成
                    auto timer = std::make_shared<steady_timer>(ioContext_, milliseconds(100));
                    timer->async_wait([timer](const boost::system::error_code& ec) {
                        if (!ec) { // 忽略被cancel的情况
                            std::cout << "Timer hit" << std::endl;
                        }
                    });
                    receive();
                } else {
                    std::cout << "Error receiving data from client" << std::endl;
                }
            });
    }

    io_context& ioContext_; // 改为引用,复用main中的io_context
    udp::socket localSocket_;
};

修改说明

  • 类中保存传入的io_context引用,确保所有异步操作都绑定到同一个被run的io_context实例。
  • 使用std::shared_ptr管理定时器和缓冲区,延长它们的生命周期,避免局部变量销毁导致异步操作被cancel或引用失效。
  • Lambda捕获改为值捕获this和buf,避免引用捕获带来的悬空引用问题。

内容的提问来源于stack exchange,提问作者Jon Lachmann

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 16:44:59