如何通过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
相关产品推荐
相关产品推荐

