推荐资料(建议按照顺序先都看完,再看本篇文章)
https://www.bilibili.com/video/BV18h41187Ep/
https://www.bilibili.com/video/BV1at4y1Q77b/
https://bbs.huaweicloud.com/blogs/277728
https://blog.csdn.net/dreamispossible/article/details/91345391
小林coding计网p242-302(看不完就算了,其实这个有些地方讲得太细了,面试不会考那么细……的吧?/(ㄒoㄒ)/~~)
过程
三次握手和四次挥手图解
(图的原出处忘了,应该是来自小林coding计网)
SYN包/ACK包格式
为什么tcp需要三次握手,不能是两次?
正常两次握手的流程
首先我们要思考,如果为两次握手,正常连接的情况是什么样子的。
由于只有两次,那么服务端在接收到SYN报文后就会建立established状态。客户端在接收到SYN和ACK报文后,也会建立established状态。
如果说三次握手相当于:
客户端:我有个重要信息想和你说(SYN),你有空吧?
服务端:我有空(ACK),我刚好也有个重要信息想和你说,我们现在就聊,你有空吧?(SYN)
客户端:我也有空捏(ACK)。
【随后客户端和服务端都开始讲话。】
真是一场酣畅淋漓的奔赴啊(泪目),可以看到三次握手就是非常谨慎地确保接下来双方 都能 立马 双向通信。
而二次握手就相当于:
客户端:我有个重要信息想和你说(SYN),你有空吧?
服务端:我有空(ACK),我刚好也有个重要信息想和你说,我们现在就聊,你有空吧?(SYN)
【随后服务端立马开始讲它想说的重要信息。】
这种情况会导致两个问题。首先,实际上服务端并不能确保客户端的接受能力是否正常(就是例子中的是否立马有空听他讲话),其次,更重要的,就是历史连接的初始化问题。
确保双方的发送、接收能力正常
历史连接的初始化
什么叫历史连接的初始化呢?
考虑一个场景:由于通常情况客户端都会连续发送多次 SYN 报文建立不同的连接。
因为网络拥堵、客户端短暂宕机等因素从而导致 一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端。因为只有两次握手。那么服务端会立马进入established状态,并且开始发送数据。
比喻成我上文的聊天的话,就是同时存在客户端A要跟服务端C讲重要信息1,服务端C也要和客户端A讲重要信息2。客户端B要跟服务端C讲重要信息3,服务端C也要和客户端B讲重要信息4。
如果是三次握手,则是:
客户端A:你好我有个重要信息1想和你说(SYN),你有空吧?【但是这个消息没有发出去】
客户端B:我有个重要信息3想和你说(SYN),你有空吧?【但是这个消息还没有被客户端接收】
客户端A:我有个重要信息1想和你说(SYN),你有空吧?【此时发给了服务端】
服务端:我有空(ACK),我刚好也有个重要信息2想和你说,我们现在就聊,你有空吧?(SYN)【和A试图建立连接】
此时因为上文是客户端B,所以是B接收该消息。
客户端B:你在说啥呢。听不懂,先不跟你聊了。(RST)
此时服务端就知道客户端A没空,因为他没有收到客户端A的ACK包。(和A的连接中止)
客户端B:我有个重要信息3想和你说(SYN),你有空吧?【此时发给了服务端】
服务端:我有空(ACK),我刚好也有个重要信息4想和你说,我们现在就聊,你有空吧?(SYN)
客户端B:我有空(ACK)。
随后客户端B和服务端都开始讲话。
如果是两次握手,则是:
客户端A:我有个重要信息1想和你说(SYN),你有空吧?【但是这个消息没有发出去】
客户端B:我有个重要信息3想和你说(SYN),你有空吧?【但是这个消息还没有被客户端接收】
客户端A:我有个重要信息1想和你说(SYN),你有空吧?【此时发给了服务端】
服务端:我有空(ACK),我刚好也有个重要信息2想和你说,我们现在就聊,你有空吧?(SYN)【和A试图建立连接】
随后服务端立马开始讲它想说的重要信息2。此时因为上文是客户端B,所以是B接收该消息。
客户端B:你在说啥呢。听不懂,先不跟你聊了。(RST)
随后服务端闭嘴了。(和A的连接中止)
客户端B:我有个重要信息3想和你说(SYN),你有空吧?【此时发给了服务端】
【随后服务端立马开始讲它想说的重要信息4。此时因为上文是客户端B,所以是B接收该消息。】
随后客户端B和服务端都开始讲话。
(图的原出处应该来自小林coding,但是网上找不到原图了)
所以可以看到,三次握手和两次握手相比,不会让服务端多建立一个历史连接,即多一个established状态,还让服务端白发送数据,从而浪费服务端的资源。
无法同步双方初始序列号
除了避免历史连接的初始化之外,三次握手还有一个重要作用。就是同步双方的初始序列号。
序列号在TCP连接中扮演了重要角色,它具有以下作用:
● 接收方可以消除重复的数据,确保数据的准确性。
● 接收方可以按照序列号的顺序接收数据包,保证数据的完整性。
● 序列号可以标识已经被对方接收的数据包,实现可靠的数据传输。
因此,在建立TCP连接时,客户端发送带有初始序列号的SYN报文,并需要服务器回复一个ACK报文,表示成功接收了客户端的SYN报文。然后,服务器发送带有初始序列号的SYN报文给客户端,并等待客户端的应答,这样一来一回,才能确保双方的初始序列号能够可靠地同步。
总结
三次握手不能为两次的主要原因为:
- 三次握手保证了双方都知道自己和对方具有接收和发送能力。
- 三次握手才可以阻止历史连接的初始化。(主要原因)
- 三次握手才可以同步双方的初始序列号。
面试相关题与回答
请简述下tcp三次握手?
以下回答背熟大概用时2min。
TCP协议属于传输层的重要协议之一,主要负责端到端的沟通。假设我们现在有一个客户端和一个服务端要进行通信。
服务端是一种被动通信的角色,当它启动后,会开放自己的一些端口并等待客户端连接。客户端是一种主动通信的角色,当它启动后,会主动连接指定ip地址的服务端。
假设此时两个端都已经启动,服务端开放了端口a,处于listen的状态。
- 客户端主动发起请求连接,生成一个初始化序列号ISN为x,并传输一个syn包给服务端。syn为synchronization,表示同步的意思,同时值为x。随后客户端便处于syn_sent状态。这就是第一次握手。
- 服务端收到syn包后,也生成一个初始化序列号ISN为y,并传输一个ack包和一个syn包给客户端。ack为acknowledge,表示接受的意思,在这里表示成功接收,同时值为第一次握手时客户端的序列号+1。随后服务端便处于syn_recv(syn_recieved)状态。这就是第二次握手。
- 客户端收到ack包和syn包后,便处于established状态。且会再传输一个ack包给回服务端,值为第二次握手时服务端的序列号y+1。服务端接收该包后便也处于estabished状态。这就是第三次握手。
综上,三次握手实际上就是一个建立连接的过程,在三次握手结束之后,客户端和服务端就可以通过该端口相互通信,传输数据了。
请简述下tcp四次挥手?
以下回答背熟大概用时2min。
TCP协议属于传输层的重要协议之一,主要负责端到端的沟通。tcp四次挥手实际上就是两端之间结束通信的过程。假设我们现在有一个客户端和一个服务端要断开连接,理论上任意一方都有主动断开的权利。但通常情况下是客户端主动断开连接。
- 那么客户端就会生成一个序列号x,并发送一个fin包和一个ack包给服务端。fin表示finish,结束的意思。fin包中携带了x的值。此时客户端处于fin_wait1状态。这就是第一次挥手。
- 服务端收到fin包和ack包后,便处于close_wait的状态,就是等待关闭的意思。同时发送一个ack包给回客户端,ack表示acknowledge,接收的意思,值为第一次挥手时客户端的序列号+1。客户端在接收到该ack包后就处于fin_wait2状态。这就是第二次挥手。
- 同时,服务端可能存在一些数据还需要传送给客户端。等他传输完毕,确认要断开连接时,会生成一个序列号y,并发送一个fin包给客户端。此时服务端处于last_ack状态。这就是第三次挥手。
- 客户端接收到服务端的fin包,便发送一个ack包给回服务端,值为第三次挥手时服务端的序列号+1。此时客户端处于time_wait状态,也就是等待关闭。一定时长后,服务端收到了ack包,客户端和服务端才会进入closed状态。这就是第四次挥手。
tcp三次握手时可以带数据吗?第几次握手带数据呢?
背熟以下回答,大概用时半分钟。
tcp的第三次握手时可以携带应用数据。因为此时客户端已经在前两次握手时确认了自己的接收和发送能力没有问题。也知道了服务端的接收和发送能力没有问题。第三次握手只是需要让服务端知道客户端的接受能力没有问题。所以可以将ACK报文和数据一同传输过去。
如果tcp第三次握手时的ACK报文丢失,会发生什么?
背熟以下回答,大概用时半分钟。
如果第三次握手时的ACK报文丢失,就相当于服务端依旧只能处于syn_recv状态。但是依旧可以建立连接并正常接收数据。因为数据中也会有ACK标识位,值和丢失的ACK报文的值是一样的。
为什么tcp需要三次握手,不能是两次握手?
背熟以下回答,大概用时两分半。
以最经典的客户端和服务端建立连接举例,三次握手不能为两次的原因为:
- 第一,三次握手保证了双方都知道自己和对方具有接收和发送能力。
- 第二,也就是最重要的理由,三次握手才可以阻止历史连接的初始化。
- 第三,三次握手才可以同步双方的初始序列号。
第一个原因很好理解,因为第三次握手为客户端向服务端发送ACK报文,省去了这个步骤,服务端收不到客户端的ACK报文,就无法得知客户端是否有正常接收的能力。
那么什么叫历史连接的初始化呢?
我们考虑一个场景:通常情况客户端都会连续发送多次 SYN 报文建立不同的连接。如果因为网络拥堵、客户端短暂宕机等因素从而导致 一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端。因为只有两次握手。那么服务端会立马进入established状态,并且开始发送数据。
客户端根据上下文,对比序列号后,发现不是它想要接收的信息。便会发送RST报文,使得服务端的连接中止。之后最新的SYN报文到达时,再继续第二次握手和第三次握手,从而成功建立连接。
在这过程中,相较于三次握手,二次握手的服务端多了一个由旧SYN报文引起的established状态,这就是历史连接的初始化。二次握手无法避免历史连接的初始化,还让服务端白发送数据,从而浪费服务端的资源。
最后,序列号在TCP连接中扮演了重要角色,它确保了数据的准确性、完整性。且可以表示哪些数据包是被对方接受过的。三次握手才可以同步双方的初始序列号,从而实现TCP可靠传输。
为什么tcp需要三次握手,不能是四次握手?
背熟以下回答,大概用时半分钟。
以最经典的客户端和服务端建立连接举例,其实tcp可以是四次握手。将原有的三次握手中的第二步,服务端接收到客户端的SYN报文后,发送ACK报文和SYN报文给客户端,拆分为两步,分别发送ACK报文和SYN报文即可。但是基于最优法则,将这两步合为一步。
为什么每次tcp连接时,初始化的序列号都要求不一样呢?
背熟以下回答,大概用时半分钟。
假设每次tcp连接时的初始化序列号都一样。那么就很大概率遇到历史报文的序列号在对方的接收窗口内的情况,从而导致历史报文被新连接成功接收,造成数据错乱。为此,初始化序列号ISN是基于时钟随机生成的,但这并不能完全避免上述问题,因为序列号会回绕,所以还需要用时间戳的机制来判断历史报文。此外,如果初始化序列号都一样的话,很容易被黑客伪造相同序列号的tcp报文用于攻击。