Skip to content

网络分层模型与数据传输过程 ​

S2-012建议学进阶约 60 分钟JDK 21计算机基础 · 计算机网络

1. 本节定位 ​

从本节起进入网络部分 ——它解释"你的请求是怎么跑到另一台机器上的" ✓:

text
应用层 → 传输层 → 网络层 → 链路层 → 物理层
HTTP      TCP      IP      以太网    电信号

★ 本节的做法:用裸 Socket 手工收发一次 HTTP ✓ ——这样能亲眼看到"应用层的文本协议"是怎么落在"传输层的字节流"上的 ✓✓

2. 学习目标 ​

  • 能说出五层模型各层的职责与代表协议 ★★★;
  • 能说清"封装与解封装"的过程 ★★★;
  • 知道端口属于哪一层、MAC 与 IP 各自的适用范围 ★★★;
  • 知道"三次握手"在代码里对应哪一行 ★★★;
  • 能说出"为什么连接要复用" ★★★。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

先看一组常见混淆 ✗ ——它们都源于"没分清哪一层负责什么":

常见说法问题
「用 IP 加端口访问服务」端口是传输层的,IP 是网络层的 —— 分属两层 ✓
「MAC 地址能定位到任意主机」 ✗MAC 只在局域网内有意义 ✓
「DNS 是网络层协议」 ✗它是应用层服务(跑在 UDP 53 上)✓
「TCP 连接就是一根线」 ✗它是双方内核里的一组状态,链路断了它并不知道 ⚠️

★ 分清层次,才能分清"问题出在哪一层" ✓✓:

text
ping 不通        → 网络层/链路层
端口连不上       → 传输层(或被防火墙拦)
连上了但报 404   → 应用层
能连上但很慢     → 可能是传输层重传、也可能是应用层慢

5. 核心概念 ​

5.1 五层模型与职责 ★★★ ​

层职责代表协议地址/标识
应用层 ★★定义"报文的含义"HTTP、DNS、SMTP、FTPURL、域名
传输层 ★★★端到端的可靠/不可靠传输TCP、UDP端口号 ✓
网络层 ★★★跨网络寻路(路由)IP、ICMP、ARPIP 地址 ✓
链路层 ★★同一局域网内送达以太网、WiFiMAC 地址 ✓
物理层把比特变成信号——

★ 三条最容易搞错的边界 ✓✓:

#要点
①端口是传输层的 ——IP 层只知道"送到哪台机器",不知道"给哪个程序" ✓
②MAC 只在局域网有意义 ——跨网段时每一跳都会换 MAC,而 IP 基本不变(除 NAT)✓
③DNS 是应用层服务 ——它自己也要跑在 UDP/TCP 之上 ✓

5.2 封装与解封装 ★★★ ​

发送方逐层加头 ✓:

text
你的数据:        GET /api/demo HTTP/1.1 …
+ TCP 头:        [源端口|目的端口|序号|确认号|标志位|…]
+ IP 头:         [源 IP|目的 IP|TTL|协议号|…]
+ 以太网头:      [源 MAC|目的 MAC|类型]
= 电信号

接收方逐层剥头 ✓:

text
电信号 → 链路层剥掉 MAC 头 → 网络层剥掉 IP 头 → 传输层剥掉 TCP 头 → 交给应用

★ 关键理解 ✓✓:

每一层只关心自己的头,不关心上层装了什么 ✓

所以 TCP 完全不知道自己在传 HTTP ——它照样能传邮件、传文件、传任意二进制 ✓✓

这就是"分层"的价值:可分头演进 ✓

5.3 实测:手工收发一次 HTTP ★★★ ​

本节的实测输出(NetworkLayersLesson):

text
【服务端】TCP 连接已建立,对端端口 = 1128
【服务端】收到的原始请求文本(前 6 行):
    | GET /api/demo?from=raw HTTP/1.1
    | Host: 127.0.0.1:1138
    | User-Agent: RawSocketClient/1.0
    | Accept: */*
【服务端】已回写 136 个字符的响应

【客户端】收到的原始响应文本:
    | HTTP/1.1 200 OK
    | Content-Type: application/json; charset=UTF-8
    | Content-Length: 31
    | Connection: close
    |
    | {"msg":"hello from raw socket"}

★ 这张输出说明三件事 ✓✓:

#说明
①HTTP 不过是"按约定格式写的文本" ——没有任何魔法 ✓
②TCP 只负责把这些字节按顺序、不丢地送到对端 ✓
③Content-Length: 31 是必需的 ——否则对方不知道"读到哪算读完" ✓✓(《TCP 与 UDP》 的粘包问题正源于此)

5.4 三次握手:代码里的体现 ★★★ ​

实测:

text
【服务端】accept() 返回 → 握手完成,连接进入 ESTABLISHED
【客户端】new Socket(...) 返回耗时 8 ms

★ new Socket(...) 这一行背后发生了三件事 ✓✓:

text
客户端 → 服务端: SYN(我想连)
服务端 → 客户端: SYN + ACK(可以,我也准备好了)
客户端 → 服务端: ACK(确认)

★ 为什么是三次 ✓✓:

次数能否满足
两次 ✗服务端无法确认"客户端能收"(客户端可能没收到 SYN+ACK)✗
三次 ✓双方都确认了"对方能收、也能发" ✓
四次多余(服务端的 SYN 和 ACK 可以合并)✓

★ 由此得到一个重要工程结论 ✓✓:

建立连接要花 1.5 个 RTT ——本机是 8 ms,跨机房就是几十毫秒 ✗

所以"频繁建连"是性能杀手 → 必须用连接池 / 长连接 ✓✓

数据库连接池、HTTP keep-alive、HTTP/2 多路复用,根本动机都在这里 ✓

5.5 从"分层"到"排障" ★★ ​

网络问题的排查顺序(从下往上) ✓✓:

步骤命令能发现什么
① 本机协议栈ping 127.0.0.1网卡驱动是否正常 ✓
② 网关/链路ping 网关IP、arp -a局域网是否通 ✓
③ 路由与跨网ping 8.8.8.8路由是否通(《DNS、IP、端口与 ARP》)✓
④ DNSnslookup 域名域名能否解析 ✓
⑤ 端口可达telnet IP 端口 / Test-NetConnection防火墙是否放行 ✓
⑥ 应用层curl -vHTTP 状态码与头 ✓

★ 一条经验 ✓:

"连不上"和"连得上但慢"是两类问题 ✓✓

连不上:按上面的顺序从下往上查 ✓

慢:先看是"建连慢"还是"传输慢" ——curl -w 能拆出各阶段耗时 ✓(《HTTP 请求响应与 Servlet》 用过)

6. 递进示例 ​

6.1 最小示例:手工发一个请求 ​

java
try (Socket socket = new Socket("127.0.0.1", 8080)) {
    socket.getOutputStream().write(
            "GET / HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n"
                    .getBytes(StandardCharsets.UTF_8));
    String response = new String(socket.getInputStream().readAllBytes());
}

6.2 业务示例:用 curl 拆分阶段耗时 ​

bash
curl -o /dev/null -s -w "DNS:%{time_namelookup}s 连接:%{time_connect}s TLS:%{time_appconnect}s 首字节:%{time_starttransfer}s 总计:%{time_total}s\n" https://example.com

★ 输出能直接告诉你"慢在哪一层" ✓✓

6.3 反例:以为 TCP 连接是一根"线" ​

text
拔掉网线再插上 —— TCP 连接可能还在(如果超过了重传次数才会断)✗
更典型的是:**NAT 网关的超时静默回收连接,双方都不知道** ✗✗
所以长连接必须靠"心跳"来探活 ✓

7. 完整可运行代码与实测 ​

代码:labs/cs-basics/src/main/java/cn/kb/learn/cs/net/NetworkLayersLesson.java

运行:

powershell
cd labs
.\mvnw.cmd -pl cs-basics compile
java "-Dstdout.encoding=UTF-8" -cp cs-basics/target/classes cn.kb.learn.cs.net.NetworkLayersLesson

实测输出(节选):

text
===== 1. 用手工 Socket 收发一次 HTTP =====
【服务端】TCP 连接已建立,对端端口 = 1128
      | GET /api/demo?from=raw HTTP/1.1
      | Host: 127.0.0.1:1138
【服务端】已回写 136 个字符的响应

===== 3. 三次握手在代码里的体现 =====
【服务端】accept() 返回 → 握手完成,连接进入 ESTABLISHED
【客户端】new Socket(...) 返回耗时 8 ms

★ 三条可落地的结论 ✓:

#结论
①排障要按层来:ping → telnet 端口 → curl ✓
②建连成本是 1.5 RTT,必须复用连接 ✓
③应用层协议自己负责"消息边界"(Content-Length / 分隔符 / 长度字段)✓

8. Debug 观察点 ​

  1. 把 HTTP 请求里的 Host 头去掉:观察 HTTP/1.1 服务端报 400 ★;
  2. 把 Content-Length 去掉(在响应里):观察客户端一直等待(不知道读完没)✗;
  3. 用 curl -v 访问同一个服务:对比它发的报文与你手写的差异 ★;
  4. 用 netstat -ano | findstr <端口>:观察连接的四个要素(源 IP:端口 ↔ 目的 IP:端口) ★★;
  5. 用 curl -w 拆分阶段耗时,观察 DNS / 建连 / 首字节 各占多少 ✓。

9. 常见错误 ​

#错误说明
1把端口说成网络层的 ✗✗端口是传输层的 ✓
2以为 MAC 能全球定位 ✗✗只在局域网内有效 ✓
3把 DNS 归到网络层 ✗它是应用层服务 ✓
4以为 TCP 连接是"物理连接" ✗✗它是内核里的状态,NAT 可能静默回收 ✓
5忘了 Host 头 ✗HTTP/1.1 必需 ✓
6忘了 Content-Length ✗✗对方不知道读到哪算完 ✓
7每请求新建连接 ✗✗1.5 RTT 的成本 ✓

10. 动手实验 ​

目标:亲手把"分层"从图变成你验证过的事实。

步骤:

  1. 跑 NetworkLayersLesson,看原始报文 ✓;
  2. 用 curl -v 访问同一个服务,对比两者发的头 ★;
  3. 给响应加上 Content-Length: 999(大于实际),观察客户端卡住 ✗★;
  4. 用 netstat -ano 看连接的四元组 ✓;
  5. 用 telnet 手工敲一个 HTTP 请求(Linux/WSL 下 nc,Windows 下可用 telnet)★★。

验收方式:能说出"一个请求从浏览器到服务端经过了哪几层、每层加了什么头"。

扩展任务:用 curl -w 做一次"分层耗时分析":

  • 访问一个真实网站,拆出 DNS / 连接 / TLS / 首字节 / 总计 ✓;
  • 连续访问两次,对比第二次是否少了 DNS 与建连的时间 ✓;
  • 说明:这就是"缓存与连接复用"的价值 ★★

11. 练习题 ​

  1. 判断:端口号属于网络层。(对 / 错)
  2. 填空:五层模型是 、、、、______;MAC 属于 ______ 层。
  3. 简答:为什么 TCP 不需要知道自己在传输什么内容?
  4. (代码阅读)
java
socket.getOutputStream().write("hello".getBytes());
// 服务端
int n = in.read(buffer);
System.out.println(new String(buffer, 0, n));

服务端一定能一次读到 "hello" 吗? 5. (编程)用 ServerSocket + Socket 写一个最小 HTTP 服务:能返回固定 JSON,且带正确的 Content-Type 与 Content-Length。

参考答案(要点)
  1. 错。端口是传输层的 ✓★
  2. 应用层、传输层、网络层、链路层、物理层;链路层 ✓
  3. 因为分层只要求"同层对话" ——TCP 只管把字节按序可靠送到对端,内容含义由应用层自己解释 ✓★
  4. 不一定 ✗ ——TCP 是字节流:"hello" 可能分两次到达,也可能与其他数据粘在一起(《TCP 与 UDP》)✓
  5. 要点:响应必须包含状态行、Content-Type、Content-Length、空行、体 ✓

12. 掌握标准 ​

  • [ ] 能说出五层各层的职责与代表协议 ★★★;
  • [ ] 能说清封装与解封装的过程 ★★★;
  • [ ] 知道端口/ IP / MAC 各属于哪一层、各在什么范围内有效 ★★★;
  • [ ] 知道 new Socket() 背后是三次握手,成本 1.5 RTT ★★★;
  • [ ] 知道应用层必须自己定义消息边界 ★★★;
  • [ ] 会按层排查网络问题(ping → telnet → curl)★★。

13. 扩展资源 ​

14. 下一步学习 ​

《TCP 与 UDP》:两种传输方式的取舍,以及"粘包"到底是怎么回事。

前置知识

无,可直接开始

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引