前四节把四层和各层职责都拆完了,这一节把它们拼起来跑一遍真实通信:开口前先打招呼(三次握手建连接)→ 说一句要对方应一声(每块数据都要确认)→ 对方没应声就重说一遍(超时重传)→ 挂断也要两边都道别(四次挥手)。一句话记住:TCP/IP 通信 = 先约会、再对账、最后道别。
一、全景:一次通信从头到尾
把 3.1~3.4 拼成一条时间线:
① 应用层(3.3) 浏览器说:"我要这个网页"
② 传输层(3.3) 先建连接(本节重点)→ 把请求切成块、编号
③ 网际层(3.2) 每块加 IP 头,写上源/目标门牌、交给下一跳
④ 网络接口层(3.2)加帧头,变成电信号/光信号上路
⑤ 线路上(1.4) 逐跳接力,一格一格交棒到对端
⑥ 接收方(3.4) 自下而上逐层脱信封,应用层拿到请求
⑦ 服务器生成响应,反过来再走一遍 ②~⑤
⑧ 传输完毕 关闭连接(本节的最后一节)
本节专门看 ②怎么开、③之前怎么确认对方真收到了、⑧怎么关——也就是标题说的"接收和传输数据的规则"。
二、开口前:三次握手(先把手搭上)
TCP 不是"拿起来就说",而是先建立连接。建立的过程固定三步:
| 次序 | 谁发 | 报文 | 说的是什么 |
|---|---|---|---|
| ① | 客户端 | SYN | "我要连你,我的起始序号是 X" |
| ② | 服务器 | SYN + ACK | "收到,同意;我的起始序号是 Y" |
| ③ | 客户端 | ACK | "收到你的序号了,开始吧" |
为什么两次不够?
- 一次:发完就走,压根不知道对方在不在、听不听得见(UDP 就这风格)
- 两次:客户端能确认"我能发、服务器能收";但服务器还不知道客户端能不能收——万一客户端那条方向是断的呢?
- 三次:双方都确认了"我发得出去、我也收得到" → 可以放心开聊
生活类比:打电话的前三句——"喂,听得到吗?"→"听得到,你听得到我吗?"→"听得到,开始吧"。
三次握手顺便把初始序号报给对方。没有序号,后面"确认 + 重传"那套对账全无从谈起(序号就是 3.4 包头上那个"第几块")。
三、传输中:发一块,确认一块(本节核心)
连接建好,开始真正传数据。规矩就四条:
- 编号——每个块都有序号,顺序清清楚楚(收 3.4 的"第几块")
- 应答(ACK)——接收方每收到一块,就回一句"第 N 块我收到了"
- 超时重传——发送方发出去就开始计时,到点还没等到确认,就当它丢了,重发这一块
- 按序重组——就算 3 号先到、2 号后到,也按序号排整齐再交给程序
为什么这套规矩值钱?
- 互联网本来就是"尽力而为"的(1.4 的逐跳接力、3.2 的 IP 不补丢),线路丢包、乱序是常态,不是异常
- TCP 用"确认 + 重传"这套对账机制,把一条不可靠的线路包装成了可靠的服务——这正是 3.3 说的"可靠是花钱买来的服务"
- 代价也明摆着:每次都要等一个来回(时间开销)、发出去的数据得留一份副本等着可能的重传(空间开销)
那时在 Socket 里 os.write(...) 写完就以为对方收到了。真相是:底下 TCP 正在默默做"确认 + 重传",你在 Java 里一个字都看不见。上层能拿到一根可靠的管子,是因为下层一直在对账。
四、接收方也有规矩:窗口与流量控制
收发是双方的事,规则不只约束发送方:
- 窗口(Window):接收方在每个确认里附带一句"我还能接 N 字节"
- 接收方的缓冲区快满了 → 窗口报小,甚至报 0 → 发送方暂停发送,等它腾出地方
- 意义:保护接收方不被撑爆——快的一方不能欺负慢的一方
互联网两端的速度、带宽、线路质量千差万别,TCP 靠"确认 + 窗口"动态调整自己的发送节奏,不需要谁去统一配置。这就是 3.1 说的"通用语言"落到实处:规矩一样,节奏各调各的。
五、收尾:四次挥手(道别也要两边都说完)
聊完了要断开,TCP 的断开也是固定流程:
客户端 ── FIN ──────→ 服务器 "我说完了"
客户端 ←── ACK ────── 服务器 "知道了"
客户端 ←── FIN ────── 服务器 "我也说完了"
客户端 ── ACK ──────→ 服务器 "知道了"
为什么握手是三次、挥手却是四次?
因为 TCP 是全双工的——两个方向各有一条独立通道,握手时服务器把"同意"和"我的序号"合并在一个报文里(SYN + ACK 合体),而关闭时每个方向都要各自关一次,没法合并,所以是 4 次。
生活类比:挂电话时不能自己说一句就按掉——要等对方也说完,不然就像电话那头明明还有人讲话,你这边已经挂线了。
六、动手 Demo19:亲眼看着连接的一生
-
打开命令行(Win+R → cmd)
-
先记一个底数:
netstat -an | findstr ESTABLISHED -
列出当前所有"已接通"的连接(3.3 的 Demo17 见过它)
-
打开浏览器访问一个网站,再跑一遍
- 会多出若干条到
:443(加密网页的知名端口,3.3 的老朋友)的ESTABLISHED -
每一条都是一次三次握手换来的
-
再看连接怎么消失:关掉浏览器标签,马上再跑一遍
- 有些条目会变成
TIME_WAIT—— 意思是"我已经挥手道别了,还在原地等一会儿" -
等这个状态是为了确保对方收到最后的确认,不是故障
-
看一次完整的请求与应答(Win10 1803 以上自带 curl):
curl -v https://www.baidu.com -v是详细模式:能看到Connected to ...(连接建好了)、请求头、响应头、状态码-
这正是 1.6 里 F12 看到的"应用层对话",只不过这次是在命令行里裸看
-
30 秒观察:反复刷新同一个网页,
TIME_WAIT的条数会往上堆——这不是故障,是正常道别的痕迹。攒到成千上万才需要调优(把那台机器同时能用的连接号配额耗光了)。
- 三次握手的三个报文分别是什么?为什么两次不够?
- TCP 怎么知道某一块数据丢了?丢了以后怎么办?
- 接收方的"窗口"是干什么用的?
- 建立连接是三次、断开是四次,差异从哪来?
答案: ① SYN、SYN+ACK、ACK。两次的话服务器无法确认"客户端那条方向也能收",三次才能让双方都确认收发双向通畅。 ② 发出去就开始计时,超时没等到 ACK 就判定丢了,重传这一块;接收方按序号重组。核心思想:没有确认 = 没送到。 ③ 告诉发送方"我还能接多少字节",防止接收方缓冲区被撑爆(流量控制),快的不能欺负慢的。 ④ 因为 TCP 是全双工,两个方向各自独立关闭,每个方向都要"我说完了 + 你收到"两下;握手时"同意"和"我的序号"能合并成一个报文(SYN+ACK),所以少一次。
常见误区:
- ❌ "发出去就等于送到了" → 在 TCP 里,没有确认就不算数
- ❌ "重传是路由器或网际层负责的" → IP 只尽力而为(3.2 就说了"丢了不补"),重传是 TCP 的活
- ❌ "三次握手是为了加密安全" → 握手只解决"双方都在 + 序号对上";加密是 TLS 的活(1.6 的分层:各管一段,别混为一谈)
- ❌ "UDP 也得握手" → UDP 没有连接的概念,打包就扔(3.3),所以快,但没人对账
- ❌ "TIME_WAIT 是连接没关干净、是故障" → 它是正常的道别收尾状态,等一会儿才消失
七、下一步学什么
- ☑ 3.6 数据的称谓(信息、数据段、数据包、数据帧) ✅ 已完成 —— 点此复习:本节对账用的"数据段",到那一节有了正式学名
相关笔记
- 数据的称谓(信息、数据段、数据包、数据帧) —— 下一节:本节发出去的"数据段",到那里有了正式学名
- 收发数据时的规则(数据打包) —— 包里那些"序号、校验",本节就是拿它们来对账的
- 为驱动应用程序而做准备的网络分层(传输层与应用层) —— TCP/UDP 的老家,本节是 TCP 规则的特写
- 负责传输数据的网络分层(网络接口层与网际层) —— IP 尽力而为留下的坑,本节看到 TCP 怎么填
- 网络的网络(互联网) —— 逐跳接力的接力赛式传递,正是"线路不可靠"的来源
- TCP通信 —— 当年写 write/read 时,底下正在做本节这套对账