
tcpdump 抓包时为什么看不到重传?
因为默认只抓经过网卡的原始包,而内核 TCP 栈的重传行为发生在发送队列中——重传包可能根本没走到网卡(比如被 TCP_QUICKACK 或 tcp_retries2 限制住),或者被 GSO/GRO 合并/拆分后形态失真。直接抓 lo 或 eth0 往往漏掉关键重传帧。
实操建议:
- 用
tcpdump -i any port <port> -w trace.pcap</port>抓全接口,避免绑定单个设备 - 加
-S显示绝对序列号,否则相对序号在重传分析中完全不可用 - 配合
ss -i查看连接当前retrans计数器,确认内核是否已记录重传事件 - 禁用 offload:运行
ethtool -K eth0 gso off tso off gro off,防止网卡篡改 TCP 头部
Go 程序里怎么拿到重传统计?
Go 标准库 net.Conn 不暴露底层 TCP 状态,但 Linux 的 getsockopt 支持读取 TCP_INFO 结构体,其中 tcpi_total_retrans 字段就是累计重传次数。必须用 syscall.GetsockoptTCPInfo 或封装好的第三方库(如 golang.org/x/sys/unix)。
常见错误现象:调用 GetsockoptTCPInfo 返回 EINVAL —— 这通常是因为连接还没完成三次握手,或 socket 类型不是 AF_INET/SOCK_STREAM。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 只对已建立的连接调用,例如在
http.HandlerFunc中通过http.Request.RemoteAddr获取 conn 后用net.Conn.(syscall.Conn)转换 - 注意
TCP_INFO在不同内核版本字段偏移不同,优先用unix.TCPInfo而非手写 struct - 不要高频轮询,
tcpi_total_retrans是单调递增计数器,两次采样差值才是本次区间重传量
为什么 net/http.Client 默认不暴露重传指标?
因为 HTTP 抽象层刻意屏蔽了传输细节:重传对上层是透明的,只要最终 ACK 到达,Response.Body.Read 就不会报错。但这也导致你无法区分「慢」是因为网络丢包重传,还是服务端处理慢。
性能影响很实际:一次重传平均增加 ~200ms 延迟(按默认 rtt=50ms + 指数退避),连续 3 次重传可能触发连接中断。而 Go 的 http.Transport 会复用连接,旧连接的重传历史会影响新请求的 RTT 估算。
实操建议:
- 在
RoundTrip前后用ss -i sport=<port></port>扫描对应连接,提取retrans字段做快照比对 - 给
http.Client加Transport.DialContext回调,在net.Dial返回后立即抓一次TCP_INFO作为 baseline - 避免用
http.DefaultClient,它共享 Transport,重传统计会被多个 goroutine 混淆
Go 代码里模拟重传来验证诊断逻辑?
不能靠发包工具伪造,得从内核协议栈层面干扰。最稳的方式是用 tc(traffic control)在本地注入丢包,触发真实重传路径。
使用场景:验证你的重传采集逻辑是否能在连接存活期间持续捕获增量,而不是只读初始值。
实操建议:
- 先查本机出口设备:
ip route | grep default | awk '{print $5}',假设输出eth0 - 注入 10% 丢包:
sudo tc qdisc add dev eth0 root netem loss 10% - 跑 Go 客户端发 10 次请求,再用
ss -i看目标连接的retrans是否上升 - 记得清理:
sudo tc qdisc del dev eth0 root,否则后续所有流量都受影响
容易被忽略的是:Go 的 http.Transport 默认启用 keep-alive,一次连接复用多次请求,重传计数会累积在同一个 socket 上——所以诊断时别只看单次请求,要盯住连接生命周期内的总重传量。










