TCP 实习生的大考
实习第二天,工作台突然贴出“今日保养”。老司机背着手走过来:“机器歇了,你手动给我过一遍。”三次握手、四次挥手……实习生的冷汗下来了。
保养日
计算历:7e3 年 af 月 6e 日。天气:晴。在 Chrome 实习的第二天。
实习生哼着小曲走进办公室,却发现工作台上贴着一张纸条:
今日保养,暂停使用
“保养?” 实习生愣了一下,“那今天的报文怎么办?”
“报文照旧,手动的。” 身后传来了老司机的声音。老司机背着手,慢悠悠地走过来,“机器再好也有歇菜的时候,基础可不能丢。来,今天考考你。”
实习生的冷汗“唰”地一下就下来了。昨天还在心里偷着乐:终于摆脱学校啦,前辈的工作台也太炫酷了,不知道在 Firefox 实习的同学怎么样;至于去 IE 那儿的同学,估计还在手动操作吧……
没想到,今天轮到自己手动了。
第一课:TCP 是干什么的
“先说说,TCP 是干什么的?” 老司机拉了张椅子坐下。
“这个我会!” 实习生翻开笔记本,挺直了腰板。
“TCP,全称 Transmission Control Protocol,即传输控制协议,是传输层协议。它的目标是在底层网络可能丢包、乱序或重复的情况下,提供可靠、有序的字节流传输服务。”
“这里的可靠,意味着在连接可用的前提下,通过确认、重传、排序和校验等机制,把应用层的数据完整、有序地交付给对方。”
“对于没有得到确认的数据段,发送方会按重传机制再次发送,直到收到确认或连接被判定为失败。”
“嗯,背得挺熟。” 老司机点点头,“那报文的组成呢?别翻笔记,画出来。”
实习生硬着头皮画了起来,涂涂改改好几回,总算画全了:

“照着图,把每个字段是干什么的,一个个说清楚。”
实习生一边回忆一边答,老司机把答案记成了一张对照表:
| 区域 | 占用位数 | 含义 |
|---|---|---|
| 源端口 | 16 | 发送端应用所使用的端口号,也就是工作间的门牌号。 |
| 目标端口 | 16 | 接收端应用所使用的端口号,也就是通道另一侧的门牌号。 |
| 序号 | 32 | 当前报文中所携带数据中第一个字节的序号,对方会根据该序号进行数据的拼接,号码是多少不重要,重要的是要连续。 |
| 确认序号 | 32 | 确认报文中使用,告知对方该序号前的报文已成功接收。 |
| 数据偏移 | 4 | 由于 TCP 首部长度不确定,对方根据该字段就可以知道 TCP 首部长度。 |
| 保留位 | 6 | 目前未使用,置 0 即可。 |
URG |
1 | 紧急标志位,标志该报文是否需要优先处理,主要是对自己这边的进程起作用。 |
ACK |
1 | 表明当前为确认报文,告知对方确认序号之前的数据已完整接收。 |
PSH |
1 | 推送标志位,提示接收端尽快把已收到的数据交给应用处理。 |
RST |
1 | 通道重置标志位,通知对方重置 TCP 通道。 |
SYN |
1 | 同步标志位,通知对方开启 TCP 通道。 |
FIN |
1 | 结束标志位,通知对方请求单向关闭 TCP 通道。 |
| 窗口 | 16 | 告诉对方自己当前还能接收多少数据,用于流量控制。 |
| 校验和 | 16 | 验证当前数据的完整性,由己方生成,并在对方校验。 |
| 紧急指针 | 16 | 与 URG 配合使用,标志紧急数据的大小。 |
| 选项 | 不固定 | 一些其他选项,为 TCP 发展后的一些补充。 |
| 填充 | 不固定 | 使 TCP 首部长度对齐到 32 位的整数倍。 |
| 数据 | 不固定 | 需要传输的数据。 |
“选项里还有不少内容。” 老司机在表尾敲了敲,“明天主要复习这块,今天先过。”
第二课:连接与释放
“TCP 既然表现为一个通道,通道的连接与释放当然是重中之重。” 老司机说道,“先说说,握手为什么是三次?”
“网络环境复杂。建立连接时,双方不仅要确认报文能够往返,还要同步各自的初始序号,这就是三次握手的核心目的。” 实习生边回忆边答。
“光背结论可不行。第一次握手之后,双方各自确认了哪些能力?画出来。”
实习生想了想,画了一张表:
第一次握手(客户端请求建立连接):
| * | 客户端发送能力 | 客户端接收能力 | 服务端发送能力 | 服务端接收能力 |
|---|---|---|---|---|
| 客户端 | × | × | × | × |
| 服务端 | √ | × | × | √ |
“客户端虽然发送了请求,但却不清楚自己是否真的将报文发送到了服务端,因此客户端并不知道自己和对方的能力。” 实习生指着表格解释,“服务端接收到了报文,就能知道客户端有能力发送,自己有能力接收。”
“第二次呢?”
第二次握手(服务端收到报文,确认连接并请求连接):
| * | 客户端发送能力 | 客户端接收能力 | 服务端发送能力 | 服务端接收能力 |
|---|---|---|---|---|
| 客户端 | √ | √ | √ | √ |
| 服务端 | √ | × | × | √ |
“若返回的报文通过验证,客户端就能确认:自己的请求已被服务端收到,服务端的响应也能够到达自己,双方都具备了收发能力。” 实习生顿了顿,“但服务端和第一次握手时一样,虽然发了报文,却不知道报文是否能正确到达。”
“所以还需要第三次。”
第三次握手(客户端收到报文,确认连接):
| * | 客户端发送能力 | 客户端接收能力 | 服务端发送能力 | 服务端接收能力 |
|---|---|---|---|---|
| 客户端 | √ | √ | √ | √ |
| 服务端 | √ | √ | √ | √ |
“服务端收到客户端的确认后,便知道自己的响应已经到达客户端,双方的初始序号也已同步。” 实习生合上笔记本,“至此,一个稳定的通道建立成功。”
“那关闭呢?” 老司机追问,“为什么是四次挥手,不能像建立一样三次吗?”
“一个通道的关闭,和开启一样,一个巴掌拍不响,需要双方都进行确认。” 实习生掰着手指头数道:
- 第一次挥手:客户端请求关闭连接。
- 第二次挥手:服务端确认客户端的关闭请求。
- 第三次挥手:服务端请求关闭连接。
- 第四次挥手:客户端确认服务端的关闭请求。
“经过四次挥手,通道在双方都知晓的情况下销毁,而不会出现一方销毁另一方却仍存在的情况,计算机资源也可得到相应的释放。”
“至于为什么不能是三次——通道的开启是双方同时开启,而关闭却不是。当客户端关闭了通道,仅仅代表客户端不再发送数据,而服务端却可以继续发送数据,客户端仍可以接收数据。所以两端的关闭只能分开走,各自完成一次请求加确认。”
通过
“嗯,不错。” 老司机满意地站起身,做了最后一次总结。
“从逻辑上看,通道的开启与关闭都可以拆成 4 个动作:”
- 一端请求开启/关闭
- 另一端确认开启/关闭
- 另一端请求开启/关闭
- 一端确认开启/关闭
“只不过在建立连接时,第 2 步和第 3 步通常合并在同一个 SYN + ACK 报文中,因此表现为三次握手。”
“这些是基础,也是最重要的部分。” 老司机把笔记本递还给实习生,“工作台会帮忙做掉这些步骤,但万一机器出故障了呢?作为 TCP 学院的优等生,得确保数据始终能到达服务端。”
“明白!” 实习生接过笔记本,暗暗松了口气。
“对了。” 老司机走到门口,又回过头来,“工作台上还有一些功能你昨天没用到,大屏幕上的信息也没完全弄懂吧?明天早点来,继续研究研究。”
实习生刚放下去的心,又提了起来。