boost::asio串口async_read_some缓冲区大小为何影响执行速度?
核心原因
你遇到的延迟飙升问题,本质是Boost.Asio的async_read默认行为导致的:async_read会一直等待,直到缓冲区被完全填满才触发read_complete回调。当你把缓冲区设为5字节时,设备只返回4字节数据,async_read就会卡在那里等待第5字节,直到USB设备的默认超时(通常约30ms)触发,才会结束读取,这就是29.6ms延迟的来源。
而缓冲区设为1或4字节时,设备返回的数据刚好能填满(或部分填满1字节缓冲区),所以async_read会立刻完成回调,响应时间自然短。
解决方法
针对你需要同时兼顾ping低延迟和大吞吐量不丢包的需求,推荐以下几种方案:
1. 使用async_read_some替代async_read
async_read_some的行为是读取当前可用的任意字节数,不会等待缓冲区填满。不管你设置512字节的大缓冲区,还是只需要读取4字节的ping响应,它都会在有数据到达时立刻触发回调:
- 对于ping指令:设备返回4字节,
async_read_some会直接读取这4字节并回调,延迟和用4字节缓冲区时一致。 - 对于大流量场景:512字节缓冲区可以一次性读取大量数据,避免丢包。
示例代码逻辑(伪代码):
// 初始化512字节缓冲区 boost::asio::streambuf buf(512); // 异步读取可用数据 socket.async_read_some(buf.prepare(512), [this](boost::system::error_code ec, std::size_t bytes_transferred) { if (!ec) { buf.commit(bytes_transferred); // 处理读取到的bytes_transferred字节数据 // 继续发起下一次读取 async_read_some(...); } });
2. 自定义read_complete完成条件
如果坚持用async_read,可以自定义完成条件函数,让读取在满足最小字节数(比如ping需要的4字节)或者有数据可用时就结束:
// 自定义完成条件:要么读取到至少4字节,要么已读取到所有可用数据 std::size_t read_complete_condition( const boost::system::error_code& ec, std::size_t bytes_transferred) { if (ec) return 0; // 如果已经读到4字节,停止读取(满足ping需求) if (bytes_transferred >= 4) return 0; // 否则继续读取,直到缓冲区满或无更多数据 return 512 - bytes_transferred; } // 使用自定义条件的async_read boost::asio::async_read(socket, buf.prepare(512), boost::bind(&read_complete_condition, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred), [this](boost::system::error_code ec, std::size_t bytes_transferred) { // 处理数据 });
这种方式既保证ping指令能快速返回,又能在大流量时一次性读取更多数据,平衡延迟和吞吐量。
3. 配置超时兜底
如果担心极端情况下无数据导致的等待,可以配合deadline_timer设置超时,比如给ping指令单独设置10ms超时,超时后主动取消读取操作。但这只是兜底方案,优先用前两种方法从根本解决问题。
总结
优先选择async_read_some,它最适配你的场景:大缓冲区满足吞吐量,同时不会因为等待填满缓冲区导致ping延迟。如果需要更精确的读取控制,再用自定义read_complete条件。
内容的提问来源于stack exchange,提问作者jpo38

