发送是逐层加头,接收是逐层剥头。加了几次就要剥几次,层数一致字节数才对得上。
上一节讲层的规矩,这一节讲字节在层之间怎么走。 这是全书唯一一次把「发送端 → 线上 → 接收端」完整走一遍的地方。
一、发送:每层只关心自己那一段
应用层把请求交给 TCP,TCP 加上 20 字节头(源端口、目的端口、序号、确认号、窗口、校验和); 交给 IP,IP 再加上 20 字节头(源地址、目的地址、TTL、协议号);交给链路层,再加上以太网头。 每一层看到的都只是「上层给的这段字节 + 我自己的头」,它不需要理解上层内容。 这就是封装(encapsulation)。 反过来说,每层的头都是给自己这一层的对端看的:TCP 头给对端 TCP 看,IP 头给路径上的路由器看, 以太网头给相邻的交换机看。
二、接收:逐层剥,逐层校验
Demo 里服务端从 127.0.0.1:55813 收到 64 字节,
内核已经替我们剥掉了以太网头(14 字节)和 IP 头(20 字节)、TCP 头(20 字节)。
所以应用层拿到手的就是干干净净的 64 字节 GET /index.html HTTP/1.1…。
注意:剥的过程在应用里看不见,因为它们全在内核里完成。
每剥一层都要做一次校验:以太网有 FCS,IP 有头校验和,TCP 有校验和。
校验不过就丢弃——丢弃是静默的,上层只能靠超时发现。
三、这条链上的三个数量关系
记住三个数,后面各章都用得上: | 关系 | 数值 | 用在哪 | |---|---|---| | MTU 减去 IP 与 TCP 头 | 1500−20−20=1460 | MSS 上限,TCP 分段依据 | | 最小以太网帧 | 64 字节(含 18 字节头尾) | 小于它要填充,抓包会看到 60 | | 环路最大 TTL | 初始 64 或 128 | 每跳减一,减到 0 就回 ICMP 超时 | 这三条是「能算」的那一类,不依赖任何设备,可以直接验算。 真实环境里还有 PPPoE(−8)、VLAN 标签(−4)、隧道(−20 起)继续吃 MTU,这也是「本地通、 跨网不通」最常见的原因。
四、动手 Demo:抓一段真实收发,逐层拆字节
让服务端把收到的原始字节原样交出来,再按层数一遍:哪些是内核剥掉的,哪些是应用能看见的。
H.SINK.raw.clear()
with H.server() as port:
raw = H.send(port, REQ11)
got = H.SINK.raw[0]
print(u'收到 %d 字节' % len(got))
H.show_bytes(got, 96) # 文本 + 十六进制两行
实跑输出:
--------------------------------------------------------------------
一次收发,逐层拆开看字节落在哪
--------------------------------------------------------------------
服务端从 127.0.0.1:56064 收到 64 字节
文本 GET /index.html HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n
十六 47 45 54 20 2f 69 6e 64 65 78 2e 68 74 6d 6c 20 48 54 54 50 2f 31 2e 31 0d 0a 48 6f 73 74 3a 20 31 32 37 2e 30 2e 30 2e 31 0d 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f 73 65 0d 0a 0d 0a
第 1 步 链路层剥掉 14 字节以太网头(这里由内核完成,看不到)
第 2 步 网络层剥掉 20 字节 IP 头,留下 TCP 段
第 3 步 传输层剥掉 20 字节 TCP 头,留下应用数据
第 4 步 应用层 64 字节 HTTP 报文
回来的状态行 HTTP/1.1 200 OK
回来的首部行数 3 行
回来的正文 40 字节
接收是发送的反向操作:每层只剥自己那一段头,剥完交给上一层。
发送端加了几次头,接收端就要剥几次——层数一样,字节数才对得上。
第一行十六进制 47 45 54 20 就是 GET 的 ASCII。
0d 0a 是 CRLF,两个字节一组的分隔符——收尾那个空行让它变成 0d 0a 0d 0a。
第 1 步 链路层剥掉 14 字节以太网头(这里由内核完成,看不到)——这句话是本节的要点:
分层是真的,但你看不见它发生。
回来的 40 字节正文就是 <html><body><h1>hello</h1></body></html>,首部只有 3 行。
一个能跑的最小响应,就这么点东西。
30 秒实验
- 在请求里加一行
X-Test: 1,重跑,看「应用层」那行的字节数变多少。 - 把
H.show_bytes的limit改成 300,看能不能把整个请求打成十六进制。 - 算一算:如果 MTU 是 1400(PPPoE),MSS 变成多少。
常见误区
- 「应用能收到 TCP 头和 IP 头」——普通 socket 看不到,它们在内核里已经剥掉了,要用原始套接字才行。
- 「校验不过会重传」——以太网和 IP 的校验失败直接丢,重传是 TCP 的事,而且靠超时触发。
- 「MTU 1500 就能发 1500 字节数据」——要减掉 40 字节的 IP 与 TCP 头。
自测三问(点开对答案)
1. 发送时加了几次头?接收时剥几次? —— 各层一次,数目相等。链路层、网络层、传输层各一次,应用层不加头。 2. 为什么抓包看到的最小以太网帧是 60 字节,标准却说 64? —— 64 含 4 字节帧校验 FCS,网卡通常把它剥掉不给上层,所以抓包看到 60。 3. MSS 是怎么算出来的? —— MTU 减去 IP 头 20 与 TCP 头 20,以太网下是 1460。路上走完了。接下来单独看这条路的三段:IP、TCP 和 DNS。
- ☐ (本节完,下一节 1.4.1 负责传输的IP协议)
相关笔记
- 36 课 · 图解TCP/IP(第5版)(封装与解封装的完整图示)
- 📘 图解HTTP · 索引