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

如何通过Boost.Asio的posix::stream_descriptor无丢包读取TAP设备?

如何用Boost.Asio读取TAP设备且不丢包?

问题背景

我使用LaKabane的tuntap库搭配Boost.Asio的posix::stream_descriptor实现TAP设备的数据包读取与处理。由于是客户端而非服务器,采用了反复异步读取的临时方案,但存在两个核心问题:

  • 无限递归调用同一函数可能引发栈溢出;
  • 数据包接收速度不足,用sudo ping -f ff02::1%test测试时存在丢包。

现有代码如下:

#include <iostream>
#include <cstdlib>
#include <boost/asio.hpp>
#include <unistd.h>
#include "tun_tap.hpp"

void handle_packet([[maybe_unused]] const boost::system::error_code& error, [[maybe_unused]] std::size_t bytes_transferred, [[maybe_unused]] const std::array<char, 1520>& buffer)
{
    if (error)
    {
        std::clog << "Error in handle_packet: " << error.message() << std::endl;
        return;
    }
    std::clog << "Received packet of size: " << bytes_transferred << std::endl;
    std::clog << std::flush;

    // 模拟数据包处理
    sleep(5);
}

void start(boost::asio::posix::stream_descriptor& tap_device)
{
    std::array<char, 1520> buffer;

    tap_device.async_read_some(boost::asio::buffer(buffer),
                                  [&](const boost::system::error_code& error, std::size_t bytes_transferred) {
                                      start(tap_device);
                                      handle_packet(error, bytes_transferred, buffer);
                                  });
}

int main() {
    try {
        boost::asio::io_context io;
        const ::size_t mtu = 1500;
        std::clog << "Create TUN device." << std::endl;
        tun_tap dev = tun_tap("test", tun_tap_mode::tap);
        std::clog << "Set MTU to " << mtu << "." << std::endl;
        dev.set_mtu(1500);
        std::clog << "Set the TUN device up." << std::endl;
        dev.up();

        boost::asio::posix::stream_descriptor tap_device(io, ::dup(dev.native_handler()));
        start(tap_device);


        io.run();
    } catch (const std::exception &e) {
        std::cerr << "Error: " << e.what() << std::endl << "Exit program.";
        ::exit(EXIT_FAILURE);
    }
    return EXIT_SUCCESS;
}

问题修复方案

1. 解决栈溢出与悬垂引用问题

当前代码的核心问题是:

  • start函数在异步回调开头就递归调用自身,导致调用栈持续累积,最终触发栈溢出;
  • 局部变量buffer被回调捕获引用,函数返回后缓冲区销毁,引发悬垂引用的未定义行为。

修复方式:

  • 调整回调逻辑,处理完当前数据包后再发起下一次异步读取;
  • 用shared_ptr管理缓冲区,确保其生命周期覆盖整个异步操作过程。

2. 解决丢包问题

丢包的主要原因是IO线程被阻塞:handle_packet中的sleep(5)是同步阻塞操作,会占用唯一的IO线程,导致无法及时发起下一次读取,系统缓冲区堆积溢出。

修复方式:

  • 把阻塞的数据包处理逻辑放到独立线程池中执行,避免阻塞IO线程;
  • 确保IO线程始终处于可响应状态,及时处理新的读取请求。

修复后的完整代码

#include <iostream>
#include <cstdlib>
#include <memory>
#include <boost/asio.hpp>
#include <boost/asio/thread_pool.hpp>
#include <unistd.h>
#include "tun_tap.hpp"

// 创建线程池处理阻塞的数据包逻辑,线程数可根据实际需求调整
boost::asio::thread_pool worker_pool(4);

void handle_packet([[maybe_unused]] const boost::system::error_code& error, [[maybe_unused]] std::size_t bytes_transferred, std::shared_ptr<std::array<char, 1520>> buffer)
{
    if (error)
    {
        std::clog << "Error in handle_packet: " << error.message() << std::endl;
        return;
    }
    // 将阻塞操作提交到线程池,不占用IO线程
    boost::asio::post(worker_pool, [bytes_transferred, buffer]() {
        std::clog << "Received packet of size: " << bytes_transferred << std::endl;
        std::clog << std::flush;

        // 模拟数据包处理的阻塞操作
        sleep(5);
    });
}

void start(boost::asio::posix::stream_descriptor& tap_device)
{
    // 用shared_ptr管理缓冲区,避免悬垂引用
    auto buffer = std::make_shared<std::array<char, 1520>>();

    tap_device.async_read_some(boost::asio::buffer(*buffer),
        [&tap_device, buffer](const boost::system::error_code& error, std::size_t bytes_transferred) {
            // 先处理当前数据包
            handle_packet(error, bytes_transferred, buffer);
            // 无错误时继续发起下一次读取
            if (!error) {
                start(tap_device);
            }
        });
}

int main() {
    try {
        boost::asio::io_context io;
        const ::size_t mtu = 1500;
        std::clog << "Create TUN device." << std::endl;
        tun_tap dev = tun_tap("test", tun_tap_mode::tap);
        std::clog << "Set MTU to " << mtu << "." << std::endl;
        dev.set_mtu(1500);
        std::clog << "Set the TUN device up." << std::endl;
        dev.up();

        // 若tun_tap库允许直接使用native_handler,可去掉::dup避免文件描述符浪费
        boost::asio::posix::stream_descriptor tap_device(io, ::dup(dev.native_handler()));
        start(tap_device);

        io.run();
        // 等待线程池中的所有任务完成后再退出
        worker_pool.join();
    } catch (const std::exception &e) {
        std::cerr << "Error: " << e.what() << std::endl << "Exit program.";
        ::exit(EXIT_FAILURE);
    }
    return EXIT_SUCCESS;
}

额外优化建议

  • 增大系统缓冲区:通过命令sudo ip link set dev test txqueuelen 1000调整TAP设备的接收队列长度(数值按需调整),减少缓冲区溢出概率;
  • 固定长度读取:已知MTU为1500时,可改用async_read读取固定的1520字节(包含以太网帧头),确保每次读取完整数据包;
  • 多线程IO上下文:若数据包量极大,可给io_context分配多个线程,提升并发处理能力:
    boost::asio::io_context io(4); // 4个线程
    boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard(io.get_executor());
    std::vector<std::thread> threads;
    for (int i = 0; i < 4; ++i) {
        threads.emplace_back([&io](){ io.run(); });
    }
    // 主线程逻辑...
    for (auto& t : threads) t.join();
    

内容的提问来源于stack exchange,提问作者Marek Küthe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 02:20:58