天天看点

阿里面试官: HTTP、HTTPS、TCP/IP、Socket通信、三次握手四次挥手过程?(附全网最具深度的三次握手、四次挥手讲解)前言2020金三银四提前备战完整精编面试解析下载地址:https://shimo.im/docs/3Tvytq686Yyv83KX

前言

这段时间面试官都挺忙的,频频出现在博客文章标题,虽然我不是特别想蹭热度,但是实在想不到好的标题了-。-,蹭蹭就蹭蹭 :)

事实上我在阿里面试的时候确实被问到了这个问题,HTTP、HTTPS、TCP/IP、Socket通信、三次握手四次挥手过程?当时虽然思路正确,可惜最终也并不算完全答对

结束后花了一段时间整理了下思路,参考和查阅了一下资料,整理如下:

来源:编程充电宝

作者:在所不辞

问题描述

你能否讲解一下

TCP

的三次握手与四次挥手呢?

面试官如果从整体到局部入手,那我们就先讲讲整个三次握手和四次挥手的过程,但不要忘记,讲的同时应该适当体现你对该知识点掌握的深度和广度,具体怎么说,我们后面慢慢道来。

三次握手

所谓的

握手

即一次发包到接收的过程,可能从客户端发送到服务端,也可能从服务端发送到客户端。

过程描述

先上一张

TCP报文结构

图,待会我们会回来看这张图:

TCP报文结构

先上三次握手的流程图:

三次握手

接下来我们来详细讲解下上图的过程:

  • 客户主机发起连接请求,设置

    SYN

    标志位为

    1

    ,同时客户端

    随机

    选择了一个初始序号

    client_isn

    ,并且存放在

    TCP报文

    字段的

    序号

    中,如下图:
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi03MDJmZWJhZmYwZjNiNjE2?x-oss-process=image/format,png) 第一次握手:SYN报文
               
  • 接下来,当服务端接收到该报文后,会为其分配

    TCP 缓存和变量

    (这使得TCP容易受到被称为

    SYN 洪泛攻击

    的拒绝服务攻击)紧接着,服务端会返回一个

    SYNACK 报文

    到客户端,其中

    SYN

    1

    确认号

    设置为

    client_isn + 1

    ,并且选一个自己的初始序号

    server_isn

    ,并放置在

    序号

    字段中,如下图:
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi03ODJjM2JmYTM1MmI4MmVk?x-oss-process=image/format,png) 第二次握手:SYNACK报文
               
  • 当收到服务器发来的

    SYNACK

    报文段后,客户端也需要给该连接分配缓存和变量,然后再次发送一个确认报文给服务端,其中,

    SYN

    标志位设置为 ,将

    确认号

    server_isn + 1

    ,另外,此次报文可以携带负载数据:
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi04ZDZhNGQwM2ZhNmVlYTU3?x-oss-process=image/format,png) 第三次握手:ACK报文
               

细节拓展

  • 三次握手的状态转换图(建议达到能默写下来的熟练程度)
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi1jNzNkNTkwYzc1NDMwZjkx?x-oss-process=image/format,png) 三次握手状态图
               
  • 服务器为什么要使用特殊的初始序号

    server_isn

    ?这为什么是必要的呢?

这个细节和

问题深究第3题

是一致的,服务器使用

特定的初始序列号 server_isn

(从

目的地IP

端口

散列

中获取)可以用来抵御SYN洪水攻击。具体为什么,以及什么是

SYN 洪泛攻击

,问题深究部分我们会再详谈。

为了下文描述方便,我们将三次握手分别称为:

SYN 报文

SYNACK 报文

ACK 报文

问题深究

1.为什么要三次握手而不是两次?

简单来说,三次握手的目的是为了让双方验证各自的接收能力和发送能力。

  • 第一次握手,A 发送

    SYN

    B

    B

    接收到了后,能确认什么呢? 显然,

    B

    能确认

    A

    发送

    能力和

    B

    接收

    能力;
  • 第二次握手,

    B

    发送

    SYNACK

    A

    A

    接收到后,能确认什么呢?

    A

    B

    的发送能力和

    A

    自己的接收能力,此外,

    A

    收到了

    SYNACK

    ,那么说明前面

    A

    发的

    SYN

    成功到达

    B

    的手中,所以也能确认

    A

    自己的

    发送

    B

    接收

    能力;至此,

    A

    已经确认了双方各自的发送能力和接收能力都是

    OK

    的,因此转为

    ESTABLISHED

    状态;
  • 第三次握手,

    A

    ACK

    B

    B

    接收后,能确认什么呢?
    直接的,`B`能确认`A`的`发送`能力和`B`的`接收`能力,另外由于`B`能收到`ACK`说明前面发送的`SYNACK`已经成功被接受了,说明能确认`A`的`接收`能力和`B`的`发送`能力。
               

如果使用两次握手,就不能确认上述所说的四种能力,那么就会导致问题。

假定不采用第三次报文握手,那么只要B发出确认,新的连接就建立了。

现假定一种异常情况,即

A

发出的

SYN

报文段并没有丢失,而是在某些网络节点长时间滞留了,以致延误到连接释放后的某个时间才到达

B

。本来这是一个早已失效的报文段。但

B

收到此失效的连接请求报文段后,却误以为是

A

又发出一次新的连接请求,于是就向

A

发出确认报文段,同意建立连接。

由于现在

A

并没有发出建立连接的请求,因此不会理睬

B

的确认,也不会向

B

发送数据,但

B

却以为新的运输连接已经建立了,并一直等待

A

发来的数据。

B

的许多资源就这样白白浪费了。

2.两个TCP建立请求相互之间同时发起时会发生什么?建立几个连接?

首先理解题意,我们知道

三次握手

正是建立

TCP连接

的过程,我们假设 A 是客户端,B 是服务端:

rfc793-同时启动

这里先给大家讲一下上图中一些符号的含义:

  • 右箭头 (-->) :从 A 发送到 B 的 TCP 报文段,且 B 接收到了;
  • 左箭头 (<--) :从 B 发送到 A 的 TCP 报文段,且 A 接收到了;
  • 省略号 (…) :TCP 报文段仍在网络中(delayed);
  • 丢失 ("XXX") :TCP 报文段丢失或者被拒绝。
  • 注释会放在括号中;
  • TCP 状态代表了处于中间的报文段到达之后的状态(AFTER);
  • 报文段的内容只显示了序列号(SEQ)、控制符(CTL)和 ACK,其余内容被省略。

接下来我们详细来看看上图中,两个请求同时相互发起时,两个

TCP

均会经历如下状态的转换:

同步请求的状态转换

上述的状态转换图可以跟前面的三次握手的转换图进行对比理解,同时发起的两个请求最终

只会建立一个连接

SYN-RECEIVED

SYN-RCVD

是一样的,前者

rcf793

中的描述方法,后者是《计算机网络-自顶向下方法》中的使用方法。

3. 客户端正在和服务端建立 TCP 连接,然而当服务器变 SYN-RCVD 后,此时一个旧的 SYN 报文 又到达了,服务器会如何处理?

其实这道题更加深挖了

TCP 建立连接

的过程,我们可以在

rfc793

中了解到详细信息。

rfc793-RST

从上图可以看到,第三行就是旧的

SYN 连接

到达服务器时,第四行是服务器照常返回,第五行是客户端给服务端发送

RST 报文

,将服务端重置为

LISTEN

我们需要从上图了解到的一点是,服务端在

SYN_RECEIVED

状态下,接收到旧的

SYN 报文

时是不能作出判断的,而是照常返回,当客户端接收到该报文后发现异常,才会发送

RST 报文

,重置连接。

关于

RST 报文

,我一开始也很疑惑,直到看到

rfc793 原文

rfc793-page33

确实说明了

TCP B

不能检测这个旧的

SYN 报文

是否正确,所以正常返回。而客户端收到会进行检测,发现是旧的报文,就会返回

RST 报文

4.第三次握手失败了怎么办?

这个问题在网上找到的答案质量参差不齐,翻阅了

rfc793

,仔细研究后,最终整理出以下答案:

首先考虑失败的情况:

ACK报文丢失导致第三次握手失败

当客户端收到服务端的

SYNACK

应答后,其状态变为

ESTABLISHED

,并会发送

ACK

包给服务端,准备发送数据了。如果此时

ACK

在网络中丢失(如上图所示),过了超时计时器后,那么服务端会重新发送

SYNACK

包,重传次数根据

/proc/sys/net/ipv4/tcp_synack_retries

来指定,默认是

5

次。如果重传指定次数到了后,仍然未收到

ACK

应答,那么一段时间后,

Server

自动关闭这个连接。

问题就在这里,客户端已经认为连接建立,而服务端则可能处在

SYN-RCVD

或者

CLOSED

,接下来我们需要考虑这两种情况下服务端的应答:

  • 服务端处于

    CLOSED

    ,当接收到连接已经关闭的请求时,服务端会返回

    RST 报文

    ,客户端接收到后就会关闭连接,如果需要的话则会重连,那么那就是另一个三次握手了。
  • SYN-RCVD

    ,此时如果接收到正常的

    ACK 报文

    ,那么很好,连接恢复,继续传输数据;如果接收到写入数据等请求呢?注意了,此时写入数据等请求也是带着

    ACK 报文

    的,实际上也能恢复连接,使服务器恢复到

    ESTABLISHED

    状态,继续传输数据。

这个结论也可以在

STACKFLOW

上找到验证:

What if a TCP handshake segment is lost?

上图圈住的部分:

总的来说,如果一个

ACK 报文

丢失了,但它的下一个数据包没有丢失,那么连接正常,否则,连接会被重置。

5.知道SYN攻击吗?如何防范?

所谓

SYN 洪泛攻击

,就是利用

SYNACK 报文

的时候,服务器会为客户端请求分配缓存,那么黑客(攻击者),就可以使用一批虚假的

ip

向服务器大量地发建立

TCP 连接

的请求,服务器为这些

虚假ip

分配了缓存后,处在

SYN_RCVD

状态,存放在

半连接队列

中;另外,服务器发送的请求又不可能得到回复(ip都是假的,能回复就有鬼了),只能不断地

重发请求

,直到达到设定的时间/次数后,才会关闭。

服务器不断为这些

半开连接

分配资源(但从未使用),导致服务器的连接资源被消耗殆尽,不过所幸,我们可以使用

SYN Cookie

进行有效地防御。

SYN Cookie

防御系统,与前面接收到

SYN 报文

就分配缓存不同,此时暂不分配资源;同时利用

SYN 报文

目的地IP

端口

,以及服务器存储的一个

秘密数

,使用它们进行散列,得到

server_isn

,然后附着在

SYNACK 报文

中发送给客户端,接下来就是对

ACK 报文

进行判断,如果其返回的

ack

字段正好等于

server_isn + 1

,说明这是一个合法的

ACK

,那么服务器才会为其生成一个具有套接字的全开的连接。

SYN Cookie 防御

当然这种方案也有一定

缺点

,最明显的就是

服务器不保存连接的半开状态

,就

丧失

重发SYN-ACK消息

的能力,这一方面会降低正常用户的连接成功率,另一方面会导致某些情况下正常通信的双方会对连接是否成功打开产生误解,如客户端发给服务端的第三次握手消息(

ACK

)半路遗失,客户端认为连接成功了,服务端认为没收到

ACK

,连接没成功,这种情况就需要上层应用采取策略特别处理了。

6.(ISN)是固定的吗?

不固定,

client_isn

是随机生成的,而

server_isn

则需要根据

SYN 报文

中的

源、ip和端口

,加上服务器本身的

密码数

进行相同的散列得到,显然这也不是固定的。

7.三次握手过程中可以携带数据吗?

讲过程的时候其实已经讲了,

第三次握手是可以携带数据的

,而前两次不行。

8. 关于 https 的认证过程?

限于篇幅,此处暂时不讲,留意后续文章。

四次挥手

握手

类似,每次

挥手

也代表一次报文的发出和接收。

因为自顶向上这本书里面的图比较简略,这里采用谢希仁版本的计算机网络中的图:

计算机网络-谢希仁-四次挥手

首先,当前客户端和服务器的状态都为

ESTABLISHED

,接下来我们详细讲解下上图的过程:

  • 客户主机发起连接释放的请求,设置

    FIN

    1

    ,当然,序号

    seq

    也会带上,这里假设为

    u

    ;发送完毕后,客户端进入

    FIN-WAIT-1

    状态。
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi1kMmYxZDE0YjgyOWRlZmY1?x-oss-process=image/format,png) 第一次挥手:FIN报文
               
  • 服务端接收到

    FIN 报文

    后,会返回一个

    ACK 报文

    回去,此时设置

    ACK

    1

    确认号

    u + 1

    ;表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。发送完毕后,服务器端进入

    CLOSE-WAIT

    状态,客户端接收到这个确认包之后,进入

    FIN-WAIT-2

    状态,等待服务器端关闭连接。
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi0xMjEwZTM2MTA5YjYzNzcz?x-oss-process=image/format,png) 第二次挥手:ACK报文
               
  • 服务器端准备好关闭连接时,向客户端发送结束连接请求,

    FIN

    置为

    1

    ;发送完毕后,服务器端进入

    LAST-ACK

    状态,等待来自客户端的最后一个

    ACK

    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi1jYTI2NDVmZjBmMjc2MGUz?x-oss-process=image/format,png) 第三次挥手:FIN报文
               
  • 客户端接收到服务端传来的

    FIN 报文

    后,知道服务器已经准备好关闭了,发送一个确认包,并进入

    TIME-WAIT

    状态,等待可能出现的要求重传的

    ACK 报文

    ;服务器端接收到这个确认包之后,关闭连接,进入

    CLOSED

    客户端等待了某个固定时间(两个最大段生命周期,`2MSL`,2 Maximum Segment Lifetime)之后,没有收到服务器端的 `ACK` ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 `CLOSED` 状态。
    
    ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xODQ1MjUzNi1kNGVlNTI1ZWMzY2JiZDY0?x-oss-process=image/format,png) 第四次挥手:ACK报文
               

这里我们再来看下

rfc793

中关于四次挥手的简单例子:

rfc793-正常的关闭例子

  • 四次挥手重要的是

    TIME-WAIT

    状态,为什么需要这个状态呢?

要确保服务器是否已经收到了我们的

ACK 报文

,如果没有收到的话,服务器会重新发

FIN 报文

给客户端,那么客户端再次收到

FIN 报文

之后,就知道之前的

ACK 报文

丢失了,就会再次发送

ACK 报文

1.为什么握手只要三次,挥手却要四次?

关键就在中间两步。

  • 建立连接时,当服务器收到客户端的

    SYN 报文

    后,可以直接发送

    SYNACK 报文

    。其中

    ACK

    是用来应答的,

    SYN

    是用来同步的。
  • 但是关闭连接时,当服务器收到

    FIN 报文

    时,很可能并不会立即关闭

    SOCKET

    ,所以只能先回复一个

    ACK 报文

    ,告诉客户端,“你发的

    FIN 报文

    我收到了”。只有等到服务器所有的报文都发送/接收完了,我才能发送

    FIN 报文

    ,因此不能一起发送,需要四次握手。

2.为什么 TIME_WAIT 状态需要经过 2MSL 才能转换到 CLOSE 状态?

  • 第一,为了保证客户端发送的最后一个

    ACK 报文

    能够到达服务器。我们必须假设网络是不可靠的,

    ACK 报文

    可能丢失。如果服务端发出

    FIN 报文

    后没有收到

    ACK 报文

    ,就会重发

    FIN 报文

    ,此时处于

    TIME-WAIT

    状态的客户端就会重发

    ACK 报文

    。当然,客户端也不能无限久的等待这个可能存在的

    FIN 报文

    ,因为如果服务端正常接收到了

    ACK 报文

    后是不会再发

    FIN 报文

    的。因此,客户端需要设置一个计时器,那么等待多久最合适呢?所谓的

    MSL

    (Maximum Segment Lifetime)指一个报文在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到

    2MSL

    时间后,客户端都没有再次收到

    FIN 报文

    ,那么客户端推断

    ACK 报文

    已经被服务器成功接收,所以结束

    TCP 连接

  • 第二,防止已失效的连接请求报文段出现在新的连接中。客户端在发送完最后一个

    ACK 报文

    后,再经过时间

    2MSL

    ,就可以使由于网络不通畅产生的滞留报文段失效。这样下一个新的连接中就不会出现旧的连接请求报文。

2020金三银四提前备战完整精编面试解析下载地址: https://shimo.im/docs/3Tvytq686Yyv83KX

继续阅读