TCP三次握手和四次挥手浅浅析

目录

  • 1 三次握手
    • 1.1 三次握手的一般情况
    • 1.2 握手的特殊情况
    • 1.3 ISN为什么需要被设计成随机的?
  • 2 四次挥手
    • 2.1 四次挥手的一般情况
    • 2.2 挥手的特殊情况

1 三次握手

1.1 三次握手的一般情况

懒得画三次握手的状态图,直接从网上截了一幅图:
TCP三次握手和四次挥手浅浅析_第1张图片
可以看到,客户端发出的SYN报文的ACK是无效的,因为此时客户端还没有任何服务端的序列号信息。

现在思考一个问题,为什么是三次握手,而不是四次握手两次握手?为了理解这个问题,我们首先要想清楚握手包的意义何在?TCP是可靠连接,它会对传输的每个字节进行编号,并配合确认重传、流量控制、拥塞控制等一系列机制来实现可靠。不难想明白每个数据报的序号是非常重要的,它直接关系到确认报文以及对失序报文的重组等。那么一次连接过程中,最开始的序列号是多少呢?这正是握手包的重要作用之一:向对方通告自己的初始序列号

不妨先总结一下握手包的作用

  • 向对方提供自己的IP以及端口号,这是建立连接所需的信息
  • 向对方通告自己的初始序列号

好了,现在可以解释为什么是三次握手了。我们可以试想一下,要是自己设计握手过程来完成上述两个作用要怎么做。不难想到,客户端要发送SYN + SEQ,来发起连接并通告自己的序列号,而报文本身就带有IP和端口号且服务器IP和端口号是知道的,因此连接所需的IP和端口号以及客户端的初始序列号就知道了,还不知道的则是服务器的初始序列号,所以服务器需要响应客户端并且向客户端通告自己的初始序列号,所以服务器向客户端回复ACK + SYN + SEQ。这样看来貌似两次握手就可以了,果真如此吗?当然不是!

要知道TCP是可靠连接,要将这个可靠性体现出来两次握手就不够了。举个例子:一个来自客户端的已经失效SYN + SEQ在网络中滞留,不过最终还是到达了服务器,此时服务器分配资源建立连接,并向客户端回复ACK + SYN + SEQ,而客户端收到这个回复后感到莫名其妙,并丢掉了这个包。显然,服务器不该建立这个连接的,然而在两次握手机制下,服务器却为连接分配了资源,这部分资源就浪费掉了(当然这种浪费不是永久的,资源最终会被回收)。所以,第三次握手包是有必要的,第三次握手包可以让服务器确定客户端已经知道了它的初始序列号(ISN),而服务器的回复(第二次握手包)则让客户端确定服务器已经知道了它的初始序列号,只有双方都确定对方知道了它的ISN,这样建立的连接才称得上是可靠的。也只有可靠的连接才能应对特殊的情况,正如上例,两次握手显然无法应对一些特殊情况。至此,我们形象的描述一下三次握手的过程:

  1. 第一次握手,客户端:服务器老哥你好,我是客户端(IP、端口号),我的ISN是xxx。
  2. 第二次握手,服务器:客户端老弟你好,你放心,我已经收到了你的ISN等信息,顺便把我的ISN发给你。
  3. 第三次握手,客户端:服务器老哥你好,你也放心,我已经收到了你的ISN(至此连接所需的信息都可靠的为对方所知,可以愉快的建立连接进行通信了)。

我们在思考一下四次握手的情况:

  1. 客户端发送SYN + SEQ
  2. 服务器回复ACK
  3. 服务器发送SYN + SEQ
  4. 客户端回复ACK

不难看出将第2、3两步合并就是三次握手的情况,虽然四次握手也能做到可靠的交换双方的信息,不过三次握手的效率更高。

综上所述,三次握手包可以实现恰到好处的让自己的信息可靠的为对方所知。若缩减为两次,则不够可靠;若增加到四次则不够恰到好处。

1.2 握手的特殊情况

TCP允许两端同时发起连接的行为,此时握手的状态图如下所示:
TCP三次握手和四次挥手浅浅析_第2张图片
这是一种特殊的情况,此时没有客户端和服务器之分,两端既是客户端也是服务器。其实这种情况是三次握手的特化,可以看出实际上双方交互了四次握手包,因为情况特殊,上文讨论的四次握手中的2、3次无法合并,因此总共需要四次握手包来完成恰到好处的让自己的信息可靠的为对方所知。

1.3 ISN为什么需要被设计成随机的?

其实一开始ISN并不是随机的,而是随系统时间增加,这样导致服务器的ISN是可预测的,这种特性可能被黑客利用,进而伪造握手包,实现TCP劫持。传奇黑客凯文·米特尼克就最早实现过这种攻击。因此ISN被设计成随机的,这是出于安全的考虑。关于这个话题的更多细节,我理解的也不深入,有兴趣的同学可以查阅更多资料。

2 四次挥手

2.1 四次挥手的一般情况

下图将挥手包和各种状态描述的很清楚了,基本的内容不再赘述,直接看图:
TCP三次握手和四次挥手浅浅析_第3张图片
同样,这里我们要思考一个问题**为什么是四次挥手?**我们同样从本质出发:挥手包的意义何在?我们都知道,挥手是为了断开连接,而TCP连接是双向的,即断开连接时要断开两个方向的连接(客户端–>服务器、服务器–>客户端);同时,TCP是可靠连接,可靠性在断开连接时也要得以体现,因此不难总结出挥手包的意义:通信双方可靠的断开通向对端的连接。不妨思考一下,如果让我们自己来设计挥手包,那么应该怎么设计才能实现上述目标?

我们先只考虑断开双向的连接:要断开一个方向的连接,我们至少要发送一个FIN包来告知对端——本端要断开到对端的连接。因此需要两个挥手包完成这项工作,也就是上图中第1、3两个挥手包。

再考虑可靠:只使用两个挥手包是不够的,两次挥手无法应对一些特殊情况。具体的说:

  • 如果客户端发送FIN包之后就断开到服务器的连接,那么当FIN包丢失时,服务器不知道客户端已经发起了关闭连接的操作,因此连接就会僵死,两端的资源也得不到释放(当然不是永久得不到释放,最终还是会被回收的)。因此第2个挥手包是必要的。加入第2个挥手包后,如果发生意外情况——FIN包或者来自服务器的ACK丢失,则客户端收不到ACK,它会重传FIN。
  • 如果第4个挥手包(LAST ACK)不存在,也即服务器发送完FIN包之后就断开到客户端的连接并释放资源,那么服务器是完事儿了,但客户端在特殊情况下仍存在问题,比如服务器的FIN包丢失,那么客户端就会卡在FIN-WAIT-2状态,连接无法彻底断开,资源也得不到释放。相反,如果加入第4个挥手包,那么在来自服务器的FIN包丢失或LAST ACK丢失时,服务器会重传FIN包。

至此,我们不难得出结论,要想实现通信双方可靠的断开通向对端的连接,至少需要4个挥手包,换句话说,4次挥手能够恰到好处的使通信双方可靠的断开通向对端的连接。挥手次数再少就不可靠,再多就冗余。

最后,我们形象的描述一下四次挥手的过程:

  1. 第一次挥手,客户端:服务器老哥,我要断开到你的连接了。
  2. 第二次挥手,服务器:客户端老弟,你放心,我已经知道你要断开到我的连接这件事了。
  3. 第三次握手,服务器:客户端老弟,我这儿也完事儿了,我要断开到你的连接了
  4. 第四次挥手,客户端:服务器老哥,你放心,我也已经知道你要断开到我的连接这件事了(至此,两端的连接彻底断开,资源也得以释放)。

2.2 挥手的特殊情况

TCP允许两端同时关闭的行为,此时挥手的状态图如下:
TCP三次握手和四次挥手浅浅析_第4张图片
不难看出,即便是特殊的情况,挥手包也在四次挥手的框架内,都是两组FIN、ACK,只不过挥手的过程是同时进行的。

你可能感兴趣的:(从lwip学TCP/IP协议栈,计算机网络,TCP,三次握手,四次挥手)