TCP 实习生的大考

实习第二天,工作台突然贴出“今日保养”。老司机背着手走过来:“机器歇了,你手动给我过一遍。”三次握手、四次挥手……实习生的冷汗下来了。

分享
TCP 实习生的大考

保养日

计算历:7e3af6e 日。天气:晴。在 Chrome 实习的第二天。

实习生哼着小曲走进办公室,却发现工作台上贴着一张纸条:

今日保养,暂停使用

“保养?” 实习生愣了一下,“那今天的报文怎么办?”

“报文照旧,手动的。” 身后传来了老司机的声音。老司机背着手,慢悠悠地走过来,“机器再好也有歇菜的时候,基础可不能丢。来,今天考考你。”

实习生的冷汗“唰”地一下就下来了。昨天还在心里偷着乐:终于摆脱学校啦,前辈的工作台也太炫酷了,不知道在 Firefox 实习的同学怎么样;至于去 IE 那儿的同学,估计还在手动操作吧……

没想到,今天轮到自己手动了。

第一课:TCP 是干什么的

“先说说,TCP 是干什么的?” 老司机拉了张椅子坐下。

“这个我会!” 实习生翻开笔记本,挺直了腰板。

TCP,全称 Transmission Control Protocol,即传输控制协议,是传输层协议。它的目标是在底层网络可能丢包、乱序或重复的情况下,提供可靠、有序的字节流传输服务。”

“这里的可靠,意味着在连接可用的前提下,通过确认、重传、排序和校验等机制,把应用层的数据完整、有序地交付给对方。”

“对于没有得到确认的数据段,发送方会按重传机制再次发送,直到收到确认或连接被判定为失败。”

“嗯,背得挺熟。” 老司机点点头,“那报文的组成呢?别翻笔记,画出来。”

实习生硬着头皮画了起来,涂涂改改好几回,总算画全了:

TCP 报文

“照着图,把每个字段是干什么的,一个个说清楚。”

实习生一边回忆一边答,老司机把答案记成了一张对照表:

区域 占用位数 含义
源端口 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 个动作:”

  1. 一端请求开启/关闭
  2. 另一端确认开启/关闭
  3. 另一端请求开启/关闭
  4. 一端确认开启/关闭

“只不过在建立连接时,第 2 步和第 3 步通常合并在同一个 SYN + ACK 报文中,因此表现为三次握手。”

“这些是基础,也是最重要的部分。” 老司机把笔记本递还给实习生,“工作台会帮忙做掉这些步骤,但万一机器出故障了呢?作为 TCP 学院的优等生,得确保数据始终能到达服务端。”

“明白!” 实习生接过笔记本,暗暗松了口气。

“对了。” 老司机走到门口,又回过头来,“工作台上还有一些功能你昨天没用到,大屏幕上的信息也没完全弄懂吧?明天早点来,继续研究研究。”

实习生刚放下去的心,又提了起来。