一文搞懂三次握手和四次挥手

TCP

TCP头部的报文结构

  • 序号:seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记

  • 确认号:ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1

序号本TCP报文数据部分的首字节序号,确认号是成功接收别人的TCP报文,并期待接收的下一个TCP报文中数据部分的首字节的序号

TCP Flags

  • URG:紧急指针标志
  • ACK:确认序号标志
  • PSH:push标志
  • RST:重置连接标志
  • SYN:同步序号,用于建立连接过程
  • FIN:finish标志,用于释放连接

需要注意的是:

  • 不要将确认序号ack标志位中的ACK搞混了。
  • 确认方ack = 发起方seq + 1,两端配对。

三次握手

TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓的“连接”,其实是客户端和服务端保存的一份关于对方的信息,如ip地址、端口号等。

TCP可以看成是一种字节流,它会处理IP层或以下的层的丢包、重复以及错误问题。在连接的建立过程中,双方需要交换一些连接的参数。这些参数可以放在TCP头部。当一个连接被建立或被终止时,交换的报文段只包含TCP头部,而没有数据

“握手”是为了建立连接,流程图如下:

对于面试中的回答,可以这样说:

  • 刚开始客户端处于 closed 状态,服务端处于 listen 状态

  • 第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN©。此时客户端处于 SYN_Send 状态。

  • 第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s),同时会把客户端的 ISN + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。

  • 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 establised 状态。

  • 服务器收到 ACK 报文之后,也处于 establised 状态,此时,双方以建立起了连接。

对于seq和ack的值,个人有个不成熟的记忆方法:

即:

若上一段请求报文有seq0,本段请求报文的 ack1 = seq0 + 1。

若上一段请求报文有ack0,本段请求报文的 seq1 = ack0。

(1和0只是标记两段请求报文)

千万不要用大白话回答!

TCP 三次握手异常情况实战分析

TCP 第一次握手 SYN 丢包

当客户端发起的 TCP 第一次握手 SYN 包,在超时时间内没收到服务端的 ACK,就会在超时重传 SYN 数据包,每次超时重传的 RTO 是翻倍上涨的,直到 SYN 包的重传次数到达 tcp_syn_retries 值后,客户端不再发送 SYN 包。

TCP第一次握手SYN 丢包

TCP 第二次握手 SYN、ACK 丢包

  • 客户端发起 SYN 后,由于第二次握手 SYN、ACK 丢包,客户端是无法收到服务端的 SYN、ACK 包,当发生超时后,就会重传 SYN 包。
  • 服务端收到客户的 SYN 包后,就会回 SYN、ACK 包,但是客户端一直没有回 ACK,服务端在超时后,重传了 SYN、ACK 包,接着一会,客户端超时重传的 SYN 包又抵达了服务端,服务端收到后,超时定时器就重新计时,然后回了 SYN、ACK 包,所以相当于服务端的超时定时器只触发了一次,又被重置了。
  • 最后,客户端 SYN 超时重传次数达到了 5 次(tcp_syn_retries 默认值 5 次),就不再继续发送 SYN 包了。

所以,我们可以发现,当第二次握手的 SYN、ACK 丢包时,客户端会超时重发 SYN 包,服务端也会超时重传 SYN、ACK 包。

简单地说:

当 TCP 第二次握手 SYN、ACK 包丢了后,客户端 SYN 包会发生超时重传,服务端 SYN、ACK 也会发生超时重传。

客户端 SYN 包超时重传的最大次数,是由 tcp_syn_retries 决定的,默认值是 5 次;服务端 SYN、ACK 包时重传的最大次数,是由 tcp_synack_retries 决定的,默认值是 5 次

TCP 第三次握手 ACK 丢包

在建立 TCP 连接时,如果第三次握手的 ACK 服务端无法收到,则服务端就会短暂处于 SYN_RECV 状态,而客户端会处于 ESTABLISHED 状态。

由于服务端一直收不到 TCP 第三次握手的 ACK,则会一直重传 SYN、ACK 包,直到重传次数超过 tcp_synack_retries 值(默认值 5 次)后,服务端就会断开 TCP 连接。

而客户端则会有两种情况:

  • 如果客户端没发送数据包,一直处于 ESTABLISHED 状态,然后经过较长一段时间才可以发现一个「死亡」连接,于是客户端连接就会断开连接。
  • 如果客户端发送了数据包,一直没有收到服务端对该数据包的确认报文,则会一直重传该数据包,直到重传次数超过 tcp_retries2 值(默认值 15 次)后,客户端就会断开 TCP 连接。

三次握手常见面试题

三次握手有什么作用?

  • 确认双方的接收能力、发送能力是否正常。
  • 指定自己的初始化序列号,为后面的可靠传送做准备。
  • 如果是 https 协议的话,三次握手这个过程,还会进行数字证书的验证以及加密密钥的生成。

(ISN)是固定的吗?

三次握手的一个重要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。

如果ISN是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。

什么是半连接队列?

服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列

当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

这里再补充一点关于SYN-ACK 重传次数的问题:

服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。

注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s…

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

第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。

为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。也就是说,第一次握手可以放数据的话,其中一个简单的原因就是会让服务器更加容易受到攻击了

而对于第三次的话,此时客户端已经处于 established 状态,也就是说,对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据页没啥毛病。

首次握手有什么隐患?

会出现SYN超时。

  • 服务端收到客户端的SYN,回复SYN-ACK的时候未收到ACK确认。

  • 于是服务端不断重试直到超时,Linux默认等待63秒才断开连接。

防护措施:

  • SYN队列满后,通过 tcp_syncookies参数会发SYN cookie【源端口+目标端口+时间戳组成】。

  • 若为正常连接则Client会回发SYN Cookie,直接建立连接。

建立连接,Client出现故障怎么办?

利用保活机制

  • 向对方发送保活探测报文,如果未收到响应则继续发送。
  • 当尝试次数达到保活探测数仍然未收到响应则中断连接。

为什么一定要进行三次握手?两次不行吗?

首先我们要弄明白三次握手的目的:

第一次握手:客户端发包,服务端收到了。

这样服务端会得出结论:客户端的发送能力、服务端的接收能力是正常的。

第二次握手:服务端发包,客户端收到了。

这样客户端会得出结论:客户端的发送、接收能力是正常的,服务端的发送、接收能力也是正常的。

但是要注意,此时服务端并不能确认客户端的接受能力是否是正常的

于是就有了第三次握手:客户端发包,服务端收到了。

这样服务端就能得出最终结论:客户端的发送、接收能力是正常的,服务端的发送、接收能力也是正常的。

如果用了两次握手,则有可能会发生下面这种情况:

客户端向服务端发出一个连接请求,但由于某种原因该连接请求报文丢失了,于是客户端又向服务端发送了一个连接请求,这次一切正常,建立连接后进行数据传输,然后释放连接,注意:客户端一共发送了两个连接请求报文段,一个丢失,一个到达服务端。但如果丢失的那个请求在某个网络节点滞留了,延迟了一会才到达服务端,这时服务端就会误以为客户端又发送了一次新的连接请求,而由于只有两次握手,此时服务端只要发出确认就能够建立新的连接了。但此时客户端会忽略服务端发来的确认,不进行数据传输,那么服务端就会一直等待客户端发送数据,造成资源的浪费

四次挥手

  • 第一次挥手:客户端发送一个FIN,用来关闭客户端和服务端的数据传送,客户端进入FIN_WAIT_1状态

  • 第二次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号 ack = seq + 1,服务端进入CLOSE_WAIT状态

  • 第三次挥手:服务端发送一个FIN,用来关闭服务端和客户端的数据传送,服务端进入LAST_ACK状态

  • 第四次挥手:客户端收到FIN后,客户端进入TIME_WAIT状态,接着发送一个ACK给服务端,确认序号 ack = seq + 1,服务端进入CLOSED状态,完成四次挥手。

    注意客户端发出确认报文后不是立马释放TCP连接,而是要经过2MSL(最长报文段寿命的2倍时长)后才释放TCP连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束TCP连接的时间要比客户端早一些

四次挥手常见面试题

为什么挥手需要四次?

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据
  • 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接,所以服务端的 ACKFIN 一般都会分开发送,从而比三次握手导致多了一次

为什么会有TIME_WAIT状态,且超过2MSL?

主动发起关闭连接的一方,才会有 TIME-WAIT 状态

TIME-WAIT 作用:等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭

原因:

1.防止关闭后又建立的新连接接收到旧连接的数据包

  • 假设TIME_WAIT等待过短,那么被复用的端口可能会建立新的TCP连接

  • 此时旧的连接在四次握手前可能有一个旧的数据包刚刚到达(图中SEQ=301)

    • 这样新的连接就会处理旧的服务端数据包,产生数据错乱等严重问题

    • 经过 2MSL 这个时间,足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的

2.等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭

  • 如果服务端发送了FIN后,没有接收到客户端的ACK报文,则会超时重传再次发送FIN。
  • 所以需要客户端等待2MSL,确保服务端接收到了ACK报文。

服务器出现大量CLOSE_WAIT状态的原因?

对方关闭socket连接,而我方忙于读或写,没有及时关闭连接。

解决措施:

  • 检查代码,特别是释放资源的代码。
  • 检查配置,特别是处理请求的线程配置。

看到这里,希望读者对于以下几个问题都有了答案。

  1. 请画出三次握手和四次挥手的示意图
  2. 为什么连接的时候是三次握手?
  3. 什么是半连接队列?
  4. ISN(Initial Sequence Number)是固定的吗?
  5. 三次握手过程中可以携带数据吗?
  6. 如果第三次握手丢失了,客户端服务端会如何处理?
  7. SYN攻击是什么?
  8. 挥手为什么需要四次?
  9. 四次挥手释放连接时,等待2MSL的意义?

参考资料:

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页