Boost.Asio示例中如何从istream十六进制打印含全头部的ICMP包
问题根因
你打印出乱码是三个问题导致的:
char类型默认带符号,字节值大于0x7F时会被符号扩展为负数,直接转十六进制会输出大量无意义的ff前缀- 没有控制十六进制输出的位宽、补零规则,也没有做16字节换行,和预期格式不符
- 用
istream::get()逐字节读二进制数据可靠性差,流遇到特殊值可能触发异常状态,而且如果你之前已经用流读了IP头、ICMP头,读指针偏移后就拿不到完整包数据了。
实现方案
先写一个通用的十六进制打印函数,严格匹配你要的输出格式:
#include <iomanip> #include <cstdint> void PrintHexPacket(const uint8_t* buf, size_t len) { std::cout << std::hex << std::setfill('0'); for (size_t i = 0; i < len; ++i) { // 转无符号类型输出,避免符号扩展,固定2位宽度不足补0 std::cout << std::setw(2) << static_cast<unsigned>(buf[i]); if (i != len - 1) std::cout << ' '; // 每16字节换行 if ((i + 1) % 16 == 0) std::cout << '\n'; } std::cout << std::dec << '\n'; // 恢复默认十进制输出,避免影响后续打印 }
然后修改你的接收回调逻辑,直接从boost::asio::streambuf拿底层原始数据,不要走流逐字节读:
void StartReceive() { gSocket.async_receive_from(gReply.prepare(65536), gReceiver, [&](const error_code& error, size_t length) { if (error) return; gReply.commit(length); // 直接获取缓冲区有效数据的指针和长度 const uint8_t* raw_pkt = boost::asio::buffer_cast<const uint8_t*>(gReply.data()); size_t pkt_len = gReply.size(); // 打印完整IP+ICMP包,输出格式和你给出的示例完全一致 std::cout << "ICMP响应包内容:\n"; PrintHexPacket(raw_pkt, pkt_len); // 后续原有解析逻辑不受影响 ipv4_header ipv4Hdr; icmp_header icmpHdr; std::istream is(&gReply); is >> ipv4Hdr >> icmpHdr; // 原有body读取、序列号校验等逻辑保留即可 gReply.consume(pkt_len); // 处理完清空缓冲区,避免数据累积 StartReceive(); // 继续投递下一个接收请求 }); }
如果需要打印发送的ICMP请求包,在你组装完发送用的streambuf后,用同样的buffer_cast方法拿到数据指针调用PrintHexPacket即可。
注意事项
- 禁止直接输出
char类型的十六进制值,必须强转为无符号类型再输出,否则必然出现符号扩展导致的乱码 - 输出十六进制时必须设置
setw(2)和setfill('0'),否则0x05这类值会被打印成5而不是预期的05 - 处理完streambuf的数据必须调用
consume()消费掉已处理的字节,否则后续接收的新数据会追加在旧数据后面,打印结果会包含历史包内容 - 原始二进制数据不要用
istream的>>运算符或者get()逐字节读取,直接操作底层缓冲区指针是最稳妥的方式,不会受流状态影响。
内容的提问来源于stack exchange,提问作者vishal kumar
相关产品推荐
相关产品推荐

