计网基础——三次握手、四次挥手

loading 2022年04月12日 85次浏览

1.三次握手

1.1 过程

一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。

  1. 第一次握手:客户端给服务端发送一个SYN报文,并指定客户端的初始化序列号ISN,此时客户端处于SYN_SEND状态

  2. 第二次握手:服务端收到SYN报文后,会以自己的SYN报文作为应答,指定服务端的ISN的同时会把客户端的ISN+1作为ACK的值,此时服务端处于SYN_RCVD状态

  3. 第三次握手:客户端收到服务端的SYN报文后,会发送一个值为服务端的ISN+1的ACK报文,表示已经收到了服务端的SYN,此时客户端处于ESTABLISHED状态服务端收到客户端的ACK报文后,也处于ESTABLISHED状态,此时连接建立完成。

SYN: 同步序列编号
ACK:确认字符
SEQ:序列号

只有第三次握手可以携带数据,因为两端已经处于ESTABLISHED状态,知道对方的接收发送能力正常

注意每次建立TCP连接的初始化序列号要不一样,否则很容易出现历史报文被下一个相同四元组(源/目的IP,源/目的端口)的连接接收的问题

1.2 为什么两次握手不够?

需要满足这些条件:

  • 客户端:
    • 客户端发,服务端收
    • 客户端收,服务端发
  • 服务端:
    • 客户端发,服务端收
    • 客户端收,服务端发

第一次握手:
服务端收到报文后,可以确认客户端发,服务端收没问题。

第二次握手:
客户端收到报文后,可以确认第一次握手成功,也就是客发服收没问题;同时也能知道服发客收没问题

第三次握手:
此时客户端再向服务端发送一个ACK,同理服务端也就知道第二次握手成功,服发客收没问题

因此,如果只有两次握手,服务端不能确定自己发送的报文是否能被客户端接收到

客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求,会发生这两种情况:

(1) 新的SYN比旧的SYN先抵达
新的SYN抵达后服务器成功确认,建立了连接。数据传输完毕后,就释放了连接。

旧的SYN后抵达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接。

不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一直等待客户端发送数据,浪费资源。

(2) 旧的SYN比新的SYN先到达
旧SYN先到达,服务器返回一个旧报文对应的SYN+ACK报文给客户端,但是客户端此时期望收到的ACK应该是新报文的,因此客户端发现发回来的报文不对后返回RST报文,服务器接收到报文后终止这一次连接。这样后续新SYN到达时就可以重新正常地进行三次握手了。

如果只进行两次握手,就无法阻止旧SYN报文初始化连接了,因为在两次握手中,服务器接收到旧SYN就直接进入ESTABLISHED状态,开始发送数据直到收到RST,造成了服务端资源的浪费。


另一种说法

  1. 三次握手才可以阻止重复历史连接的初始化(主要原因)
  2. 三次握手才可以同步双方的初始序列号
  3. 三次握手才可以避免资源浪费

1.3 SYN攻击

服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击。

SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用半连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。

半连接队列就是装SYN的队列

常见的防御方法:

  • 调大 netdev_max_backlog;
  • 增大 TCP 半连接队列;
  • 开启 tcp_syncookies;
  • 减少 SYN+ACK 重传次数

1.3 握手丢失会发生什么?

1.3.1 第一次握手丢失

当客户端想和服务端建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到 SYN_SENT 状态。

在这之后,如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发「超时重传」机制,重传 SYN 报文,而且重传的 SYN 报文的序列号都是一样的。

因此,第一次握手丢失会每隔一段时间(每次重传间隔时间是上次的两倍)触发客户端的一次超时重传,直到重传次数达到系统内设值。

1.3.2 第二次握手丢失

第二次握手的 SYN-ACK 报文其实有两个目的 :

  • 第二次握手里的 ACK, 是对第一次握手的确认报文;
  • 第二次握手里的 SYN,是服务端发起建立 TCP 连接的报文;

因此,第二次握手丢失会导致这么两个结果:

  • 客户端没收到ACK报文,以为自己没传出去,触发超时重传机制重传SYN报文
  • 第二次握手丢失了,服务端也就接收不到第三次握手,于是服务端这边也触发超时重传机制,重传SYN+ACK报文

1.3.3 第三次握手丢失

第三次握手丢失的话服务端会接受不到客户端传来的ACK报文,因此服务端以为自己没传出去,导致触发服务端的超时重传机制,重传SYN+ACK报文,直到收到第三次握手或者到达重传次数上线后断开连接。

2.四次挥手

2.1 过程

初始时双方都处于ESTABLISHED状态,假设客户端先发起关闭请求:

  1. 第一次挥手:客户端发送一个FIN报文(连接释放报文段),报文中指定一个序列号。此时客户端处于FIN_WAIT1状态。发送完毕后停止发送数据,主动关闭TCP连接,进入FIN_WAIT1状态,等待服务端确认。

  2. 第二次挥手:服务端收到FIN后,发送ACK报文表示已收到,服务端转变为CLOSE_WAIT状态。此时TCP处于半关闭状态,客户端到服务端的连接释放,客户端收到ACK报文后,进入FIN_WAIT2状态,等待服务端发出FIN报文(连接释放报文段)。

  3. 第三次挥手:服务端发送FIN报文,并指定序列号,此时服务端处于LAST_ACK状态,即没有要向客户端发送的数据了,等待客户端确认。

  4. 第四次挥手:客户端收到FIN后,同样回复ACK报文,进入TIME_WAIT状态。服务端收到ACK后,就进入CLOSED状态。此时客户端需要等待2MSL确保服务端收到ACK报文后才进入CLOSED状态

收到一个FIN只意味着在这一方向上没有数据流动。客户端执行主动关闭并进入TIME_WAIT是正常的,服务端通常执行被动关闭,不会进入TIME_WAIT状态。

2.2 为什么要四次挥手?

握手时,服务端收到客户端的SYN连接请求报文后可以同时发送SYC+ACK报文,其中ACK报文用来应答,SYN报文用来同步。

但是当挥手时,服务端收到客户端的FIN后,可能还存在没发送完的报文,所以只能先回复一个ACK报文告诉客户端已收到FIN,等自己处理完毕后才能向客户端发送FIN,因此需要四次。

2.3 TIME_WAIT/2MSL等待

2.3.1 概念

TIME_WAIT状态也称为2MSL等待状态,是主动关闭连接方才会出现的状态,每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。

2.3.2 意义

为了保证客户端发送的最后一个ACK报文能够到达服务端。因为这个ACK可能会丢失,导致处于LAST-ACK状态的服务端收不到,那么服务端就会超时重传FIN+ACK报文(也就是第三次挥手)。

若客户端能在2MSL时间内收到这个重传的FIN+ACK报文,自己也就会重传ACK,重新启动2MSL计时器,确保两端都能进入CLOSED状态,相当于提供一定的报文丢失容错率

否则如果客户端发送完ACK就立刻断连,会收不到服务端重传的FIN+ACK,自己也就不会重传ACK,服务端无法CLOSED

还有一个意义,是为了防止历史连接中的数据,被后面相同四元组的连接错误的接收。不过重点了解上面那个。

2.4 挥手丢失会发生什么?

2.4.1 第一次挥手丢失

同第一次握手丢失,客户端触发超时重传

2.4.2 第二次挥手丢失

客户端接收不到ACK,以为自己FIN没传过去,还是触发客户端的超时重传

2.4.3 第三次挥手丢失

第三次握手丢失,服务端也就收不到第四次握手,导致服务端以为自己没发出去,因此触发服务端超时重传

2.4.4 第四次挥手丢失

同2.4.3 服务端收不到第四次握手,触发服务端超时重传

2.5 服务器出现大量 TIME_WAIT 状态的原因有哪些?

首先要知道 TIME_WAIT 状态是主动关闭连接方才会出现的状态,所以如果服务器出现大量的 TIME_WAIT 状态的 TCP 连接,就是说明服务器主动断开了很多 TCP 连接。

以下场景服务端会主动断开连接:

  • 第一个场景:HTTP 没有使用长连接
    HTTP1.0中需要通过Connection字段开启,1.1后默认开启。

  • 第二个场景:HTTP 长连接超时
    假设设置了 HTTP 长连接的超时时间是 60 秒,系统就会启动一个定时器,如果客户端在完后一个 HTTP 请求后,在 60 秒内都没有再发起新的请求,定时器的时间一到,nginx 就会触发回调函数来关闭该连接,那么此时服务端上就会出现 TIME_WAIT 状态的连接。

  • 第三个场景:HTTP 长连接的请求数量达到上限
    长连接数量到达系统设定的最大值时,系统就会主动关闭这个长连接。

2.6 服务器能不能把第二次挥手的ACK和第三次挥手的FIN合并在一次发送?

上面讲了需要分开这两次挥手的原因是因为服务器可能收到客户端断开连接请求后自己还有数据要处理发送。那么如果没有数据要发送并且开启了 TCP 延迟确认机制,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手

什么是 TCP 延迟确认机制?
当发送没有携带数据的 ACK,它的网络效率也是很低的,因为它也有 40 个字节的 IP 头 和 TCP 头,但却没有携带数据报文。 为了解决 ACK 传输效率低问题,所以就衍生出了 TCP 延迟确认。:

  • 当有响应数据要发送时,ACK 会随着响应数据一起立刻发送给对方
  • 当没有响应数据要发送时,ACK 将会延迟一段时间,以等待是否有响应数据可以一起发送
  • 如果在延迟等待发送 ACK 期间,对方的第二个数据报文又到达了,这时就会立刻发送 ACK