这一篇,讨论的是著名的四次挥手。
1. 断开连接
在此之前,先看图 1,看看 TCP 是如何断开连接的.
图1 四次挥手
图 2,仍然是之前的实验中所抓取的数据包,你可以以在 unp/protocol/data/tcp_retrans.pkt
目录下找到它。
图2 抓取到的数据包
(1) 首先,由客户端调用 close,将这一端称为主动关闭(active close)。然后该端发送一个 FIN 段到对端。
(2) 接收到 FIN 段的服务器执行被动关闭(passive close)。接下来,接收到此 FIN 段的服务器回复 ACK 进行确认(实际上是由内核自动完成回复的),同时内核会传递一个文件结束符 EOF(放在缓冲区末尾) 给应用进程。
(3) 一段时间后……服务器端也没有数据要发送给对端了,调用 close,这导致服务器端也发送一个 FIN 段到对端。
(4) 客户端接收到 FIN 后,回复 ACK 进行确认。
上面的 4 个步骤就是著名的 4 次挥手。
注意:
- 执行主动关闭的一方既可以是客户端,也可以是服务器,这两者之间是对等的。
- 通常断开连接需要 4 个 TCP 段。但是有时候不一定是这样。某些情况下,步骤 1 中的 FIN 段会随着数据一起发送到对端;另一种情况,步骤 2 和步骤 3 有可能被合并成一个 TCP 段(实际抓包中,也遇到了这个情况,如图 3)。
图3 有时候会出现三次挥手的情况
2. 为什么是四次挥手
正好图 3 中所示,完全可以三次挥手就可以断开连接,为什么还要四次挥手呢?
实际上,这是 TCP 的半关闭(half-close)特性所造成的。
因为 TCP 连接是全双工的(数据在两个方向上可以同时传递,图 4),因此每个方向就必须能够单独的关闭。就比如客户端执行了半关闭操作后,只是通知服务器它没有数据要发送了,并不代表它不能接收数据。因此,只要服务器还没有主动关闭,就能够向客户端继续发送数据。也就相当于在步骤 2 和 步骤 3 之间,服务器仍然能够向客户端发送数据。
图4 TCP 连接是全双工的
四次挥手为被动关闭的一方提供了很大的伸缩空间,让被动关闭一方有机会继续向主动关闭一方发数据。**如果**TCP 协议标准要求步骤 2 和 步骤 3 合并成一个步骤,这种伸缩空间就没有了,也就是说只要有一端关闭了,另一方就没有机会继续发送数据。
【注意】
在 Linux 编程学习笔记中讲网络编程的时候,我们关闭连接用的都是函数 close. 任何一端使用函数 close 进行关闭,实际上就直接把全双工两个方向的连接全部关闭了(图 4 中的蓝色通道和红色通道)。如果只想关闭一端,只能使用函数 shutdown 来关闭一个方向的连接。关于 shutdown 函数,将在后续文章中介绍。
实际上,在图 4 中,进程 A 既可以关闭蓝色通道,也可以关闭红色通道,也可以将蓝色通道和红色通道全部关闭,这由 shutdown 的参数控制。也就是说,进程 A 既可以告诉对方我没有数据要写了,也可以告诉对方,它不想读数据了。
3. 总结
- 掌握四次挥手过程
- 理解为什么是四次挥手