第一百零八章:商船
问道期涉及内核源码:
一
林小源登上了一艘停泊在深水区的 TCP 商船。
船身比远处看到的更加庞大。甲板上堆满了货箱,每只箱子侧面都用白漆写着编号——序列号。一个身穿深蓝制服的船长站在舵轮旁,手里拿着一本厚厚的航海日志。
"欢迎上船。" 船长的声音低沉而稳重,"我是 TCP。你要运货,就得守我的规矩。"
他翻开日志,第一页画着三条波浪线。"第一步,三次握手。出航之前,我得跟对岸确认三件事——'我要来了','我知道你要来了,我也准备好了','我知道你准备好了'。少一步都不行。"
林小源看着甲板上的绞盘开始转动。一条绳索抛向对岸,对岸抛回一条,这边再确认一次。三条绳索拧成一股,牢牢地系住了两岸。
"为什么不能两次?"
船长摇头:"两次的话,我对岸发了信号说'我准备好了',但我不知道对面收没收到。万一信号丢在半路,我这边傻等,对面也不知道我在等。三次,双方都确认了对方收到了自己的信号,才够。"
他合上日志:"然后是可靠传输。每只货箱有编号,到了对岸对方得回一封信——'第 37 号箱收到了'。这叫确认,ACK。如果我等了半天没收到确认,我就重发。超时重传,这是我的底线。"
说到底,TCP 的可靠不是凭空来的——每一份确认、每一次重传,都是在用时间换安全。
二
船长带林小源走进了船舱深处。
舱壁上挂着一幅巨大的图表,上面画着一条缓慢上升的曲线。曲线的起点几乎贴着地面,然后逐渐变陡,到了某个位置突然跌落,又重新开始爬升。
"这是拥塞控制的曲线。" 船长指着图表,"我不会一出港就全速前进。一开始我只发一个小小的窗口——cwnd = 1,一个报文段。收到了确认,我就加倍——2、4、8、16……这叫慢启动。"
"慢启动?明明在加速。"
"起步慢。" 船长纠正他,"指数增长听起来快,但从 1 开始,要好几个往返才能到像样的速度。到了阈值 之后,改成线性增长——每个往返只加 1。这叫拥塞避免。"
他用手指在图表上点了一下那个跌落的位置:"如果网络堵了——丢包了、超时了——我就把速度砍掉。 设为当前窗口的一半, 重新从 1 开始。如果是收到三个重复 ACK,说明只是轻微丢包,我就用快重传——立刻重发那个丢了的包,然后快恢复—— 减半,不从 1 开始。"
林小源盯着那条曲线,忽然明白了它的形状为什么是锯齿状的——不断地试探、加速、撞墙、减速、再试探。
"为什么不一开始就全速?"
船长冷笑一声:"你要是一出港就全速冲,航道上所有的船都得被你掀翻。网络是共享的——你堵了,别人也跟着遭殃。慢启动是在问网络:'你能承受多少?' 然后一点一点加,直到摸到天花板。"
他拍了拍林小源的肩膀:"小子,记住——慢,是为了最终能更快。蛮干的人,一条航道都走不远。"
林小源忽然懂了——慢启动不是真的慢,是在试探网络的底线,先摸清楚能承受多少,再一点一点加。
三
夜深了,船长在甲板上挂起了一盏灯笼。
"你要走了?" 他问林小源,"走之前,看看四次挥手。"
他拿起四支信号旗,依次举起。
"第一旗,FIN——我说'我这边没货要发了'。第二旗,ACK——你确认'知道了'。但这时候你可能还有货要发给我,所以连接不能完全断。第三旗,FIN——你也说'我也没货了'。第四旗,ACK——我确认'好,彻底结束'。"
"为什么是四次?握手不是三次吗?"
"握手的时候,SYN 和 ACK 可以合在一条消息里。但挥手不行——我说'不发了',你可能还有东西要发给我,你不能立刻就说'我也不发了'。所以 FIN 和 ACK 不能合并,得各走各的。"
林小源接过信号旗,感受到了旗帜上残留的风的力度。每一面旗都承载着一段对话——不是数据的对话,而是控制的对话。
"船长,如果我发了 FIN 但对方一直不回呢?"
"那就等着。FIN_WAIT_2 状态,等到超时。如果对方进程崩溃了,内核会帮你发 RST 强制断开。" 船长收起信号旗,"TCP 的连接是优雅的——建立要确认,断开也要确认。每一步都有名字,每一步都有状态。你以后会学到 的。"
这下他明白了——握手可以一步到位,挥手却不行,因为两边的状态不同步,得各走各的。
/*
* TCP 的核心机制:
*
* 1. 连接建立(三次握手)
* 客户端 → SYN → 服务器
* 客户端 ← SYN+ACK ← 服务器
* 客户端 → ACK → 服务器
*
* 2. 可靠传输
* 每个包有序列号
* 接收方确认收到
* 超时重传
*
* 3. 流量控制
* 滑动窗口
* 接收方告知可用缓冲区
*
* 4. 拥塞控制
* 慢启动
* 拥塞避免
* 快重传、快恢复
*
* 5. 连接关闭(四次挥手)
* 客户端 → FIN → 服务器
* 客户端 ← ACK ← 服务器
* 客户端 ← FIN ← 服务器
* 客户端 → ACK → 服务器
*/
printf("=== TCP — 可靠的商船 ===\n\n");
printf("TCP 的核心机制:\n\n");
printf("1. 三次握手:\n");
printf(" 客户端 → SYN → 服务器\n");
printf(" 客户端 ← SYN+ACK ← 服务器\n");
printf(" 客户端 → ACK → 服务器\n\n");
printf("2. 可靠传输:\n");
printf(" 序列号: 每个字节有编号\n");
printf(" 确认号: 接收方确认收到\n");
printf(" 超时重传: 未确认则重传\n\n");
printf("3. 流量控制:\n");
printf(" 滑动窗口:\n");
printf(" 发送方 ──── [已发] [可发] [不可发]\n");
printf(" 接收方告知窗口大小\n\n");
printf("4. 拥塞控制:\n");
printf(" 慢启动: cwnd 从 1 开始\n");
printf(" 拥塞避免: cwnd 线性增长\n");
printf(" 快重传: 收到 3 个重复 ACK\n");
printf(" 快恢复: cwnd 减半\n\n");
printf("5. 四次挥手:\n");
printf(" 客户端 → FIN → 服务器\n");
printf(" 客户端 ← ACK ← 服务器\n");
printf(" 客户端 ← FIN ← 服务器\n");
printf(" 客户端 → ACK → 服务器\n\n");
printf("--- TCP 报文头 ---\n");
printf("源端口 | 目的端口\n");
printf("序列号\n");
printf("确认号\n");
printf("数据偏移 | 标志 | 窗口\n");
printf("校验和 | 紧急指针\n");#include <stdio.h>
/*
* TCP 的核心机制:
*
* 1. 连接建立(三次握手)
* 客户端 → SYN → 服务器
* 客户端 ← SYN+ACK ← 服务器
* 客户端 → ACK → 服务器
*
* 2. 可靠传输
* 每个包有序列号
* 接收方确认收到
* 超时重传
*
* 3. 流量控制
* 滑动窗口
* 接收方告知可用缓冲区
*
* 4. 拥塞控制
* 慢启动
* 拥塞避免
* 快重传、快恢复
*
* 5. 连接关闭(四次挥手)
* 客户端 → FIN → 服务器
* 客户端 ← ACK ← 服务器
* 客户端 ← FIN ← 服务器
* 客户端 → ACK → 服务器
*/
int main() {
printf("=== TCP — 可靠的商船 ===\n\n");
printf("TCP 的核心机制:\n\n");
printf("1. 三次握手:\n");
printf(" 客户端 → SYN → 服务器\n");
printf(" 客户端 ← SYN+ACK ← 服务器\n");
printf(" 客户端 → ACK → 服务器\n\n");
printf("2. 可靠传输:\n");
printf(" 序列号: 每个字节有编号\n");
printf(" 确认号: 接收方确认收到\n");
printf(" 超时重传: 未确认则重传\n\n");
printf("3. 流量控制:\n");
printf(" 滑动窗口:\n");
printf(" 发送方 ──── [已发] [可发] [不可发]\n");
printf(" 接收方告知窗口大小\n\n");
printf("4. 拥塞控制:\n");
printf(" 慢启动: cwnd 从 1 开始\n");
printf(" 拥塞避免: cwnd 线性增长\n");
printf(" 快重传: 收到 3 个重复 ACK\n");
printf(" 快恢复: cwnd 减半\n\n");
printf("5. 四次挥手:\n");
printf(" 客户端 → FIN → 服务器\n");
printf(" 客户端 ← ACK ← 服务器\n");
printf(" 客户端 ← FIN ← 服务器\n");
printf(" 客户端 → ACK → 服务器\n\n");
printf("--- TCP 报文头 ---\n");
printf("源端口 | 目的端口\n");
printf("序列号\n");
printf("确认号\n");
printf("数据偏移 | 标志 | 窗口\n");
printf("校验和 | 紧急指针\n");
return 0;
}道藏笔记
内核启示
船长说的每一条规矩都不是摆设——TCP 的可靠来自一整套机制的配合。
三次握手是建立连接的仪式:SYN 说"我来了",SYN+ACK 说"我知道你来了,我也准备好了",ACK 说"我们都确认了"。少一步都不行,两次的话服务器不知道客户端有没有收到 SYN+ACK。之后每发一批货都要确认——ACK 说"收到了",没收到就重传,这是可靠传输的底线。
拥塞控制更有意思:慢启动从 cwnd=1 开始指数增长,起步慢是为了试探网络能承受多少;到了阈值改成线性增长,小心翼翼逼近天花板;遇到丢包就减速——快重传立刻补发,快恢复把 cwnd 减半。整条曲线像锯齿一样,不断试探、加速、撞墙、减速、再试探。
四次挥手比握手复杂,因为 FIN 和 ACK 不能合并——你说"不发了",对方可能还有东西要发给你,得各走各的。
TCP 之试
TCP 商船关闭连接时,若主动关闭方等待对端 FIN,本章点名的危险状态是什么?