一个 HTTP 请求在 socket 里只是 82 字节的文本,可到了网线上它被包了好几层头。每一层多出多少字节、头里的字段是什么、小请求和大下载的头部占比差多少?这篇用 tcpdump 抓真实的包,再用代码按字节拼一遍来对照。
收到 82 字节: b'GET /hello HTTP/1.1\r\nHost: 127.0.0.1:9403\r\nUser-Agent: curl/8.7.1\r\nAccept: */*\r\n\r\n'
HTTP 请求在 socket 里就是一段文本,TCP 不理解它,只负责可靠地搬运字节流。
这个会话共抓到 14 个包(握手 3 + 请求 1 + 响应 1 + 挥手若干 + ACK……),其中携带请求的是:
lo0 链路层头: 4 字节(地址族 AF_INET=2,小端存放),没有 MAC 地址
IPv4 头: 20 字节 版本=4 头长=5x4 TOS=0 总长=134 id=0 标志=DF(True) TTL=64 协议=6(TCP) 校验和=0x0000 127.0.0.1 → 127.0.0.1
TCP 头: 32 字节 源端口=客户端临时端口 目的端口=9400 seq 相对=1 标志=PSH ACK 窗口=6380 选项长度=12 字节
应用数据(HTTP 请求): 82 字节
合计: 4(lo0) + 20(IP) + 32(TCP) + 82(HTTP) = 138 字节;头部开销占 39% (IP 总长 134)
这个包的前 64 字节(从 IP 头开始):
0000 45 00 00 86 00 00 40 00 40 06 00 00 7f 00 00 01 E.....@.@.......
0010 7f 00 00 01 f3 8b 24 b8 5e 79 0d fd 5f 47 4a 5e ......$.^y.._GJ^
0020 80 18 18 ec fe 7a 00 00 01 01 08 0a 0d 54 0f 28 .....z.......T.(
0030 bc a3 b1 f9 47 45 54 20 2f 68 65 6c 6c 6f 20 48 ....GET /hello HIPv4 头 20 字节(版本、头长、总长、TTL、协议号、校验和、地址),TCP 头 20 字节加选项(这里是 12 字节的时间戳选项,共 32 字节),链路层头在环回接口上是 4 字节,在以太网上是 14 字节(目的 MAC、源 MAC、EtherType)。
curl 返回 HTTP 200;这个会话共抓到 12 帧 以太网头: 14 字节 目的 MAC=70:85:6c:xx:xx:xx(默认网关) 源 MAC=7e:aa:6f:xx:xx:xx(本机) EtherType=0x0800(IPv4) IPv4 头: 20 字节 TTL=64 协议=6 总长=126 校验和=0x3036 目的=172.66.147.243 TCP 头: 32 字节 目的端口=80 选项长度=12 字节;HTTP 请求 74 字节 整个帧: 140 字节 = 14 + 20 + 32 + 74;同一个请求在 lo0 上是 4 + 20 + 32 + 82 en0 上 SYN 里的 MSS = [1460](= 1500 − 20 − 20);lo0 上是 16344(= 16384 − 20 − 20)
MSS = MTU − 20 − 20:环回 16344,以太网 1460。同样的 1 MiB,环回上只要 66 个带数据的包,以太网上要 725 个帧。
lo0:响应方向携带数据的包 66 个,应用数据共 1048719 字节(含 HTTP 响应头);最大的一个包的 TCP 数据 16332 字节 这些包的头部合计 3696 字节,占总字节的 0.35% 同样的 1048719 字节放到 MSS=1448 的以太网上:需要 725 个帧,头部 47850 字节(每帧 14 + 20 + 32),占 4.36%(这是按公式算出来的,不是实测) 对比第二节那次只取 12 字节响应体的小请求:14 个包共 1040 字节(含 lo0 头),应用数据只有 232 字节,占 22%——小请求里绝大部分字节是头部和握手
大下载的头部占比很小(lo0 上 0.35%,以太网上约 4.4%),小请求则很高:14 个包里应用数据只占 22%。
自己用 struct 拼出的 IPv4 头(校验和先填 0)与抓到的前 20 字节:除第 10~11 字节(校验和)外 完全相同 抓到的原始字节里 IP 校验和字段 = 0x0000(环回口发送时没有填,tcpdump 提示 bad cksum);自己按反码求和算出来应该是 0x3c70 所以在环回接口上看到的 “bad cksum” 不是出错:校验和被留空(校验和卸载),真正走网卡时才会填上
环回接口上抓到的包校验和是 0——发送时没有填,tcpdump 报 bad cksum 是校验和卸载的结果,不是错误。