<返回更多

一文带你搞定TCP挥手

2022-01-14    DifferentJava
加入收藏

摘要

  1. TCP断开连接
  2. TIME_WAIT
  3. TIME_WAIT优化
  4. TCP保活
  5. Sokcet编程

TCP断开连接

TCP断开连接,需要经历四次挥手,通信的双方都可主动断开连接,断开连接通信的双方占用的资源将会被释放。

一文带你搞定TCP挥手

 

  1. 客户端会发送一个FIN报文给服务端,然后进入FIN_WAIT_1状态
  2. 服务端在收到FIN报文后,会回复客户端段一个ACK报文,然后进入CLOSED_WAIT状态
  3. 客户端在收到服务端的ACK报文以后会进入FIN_WAIT_2状态
  4. 服务端在处理完历史数据以后会发送FIN报文给客户端,然后进入LAST_ACK状态
  5. 客户端在收到服务端的FIN报文以后,会发送一个ACK报文给服务端,然后进入TIME_WAIT状态
  6. 服务器在收到ACK报文以后,就会真正的关闭连接,进入CLOSED状态
  7. 客户端在经过2MSL时间后,也会自动关闭连接进入CLOSED状态

为什么回收需要四次

原因是客户端在主动发起FIN报文以后仅表示客户端不再主动发送数据了但是还可以接收数据。服务器在响应ACK报文以后,还有可能有数据还在处理且需要发送给客户端,因此当服务器处理完这些数据以后才能发送FIN报文表示同意关闭连接。

因此服务端的ACK和FIN报文需要分开发送,挥手也就变成了4次。

什么是MSL和TTL

TTL是IP头部中的一个字段,是指IP数据报可以经过的最大路由数,每经过一个路由器都需要减1,当TTL值为0时数据报就会被丢弃,同时发送ICMP报文给源主机。TTL的单位是路由跳数。

MSL是报文在网络中存在的最长时间,超过该时间就会被丢弃。

为什么TIME_WAIT需要经历2MSL后才可以变为CLOSED

网络中存在的发送方数据包,首先需要发送给服务端,服务端在处理完以后又会将相应发送给客户端,所以总共需要2个倍的时间。

2MSL的时间是从客户端接收到FIN报文并且发送ACK报文时开始的。如果此时ACK报文没有被服务端接收到触发了服务端的超时重传,客户端又再次收到了FIN报文,那么2MSL将重新开始计时。

linux中默认一个MSL是30s,也就是说TIME_WAIT的时间是60s。

TIME_WAIT

为什么需要TIME_WAIT状态

主动发起连接中断的一方需要有TIME_WAIT状态,主要是以下原因:

  • 防止具有相同四元组的旧数据包被收到
  • 保证最后一次ACK报文能被被动关闭连接的一方收到,也就是保证被动关闭连接的一方能被正确关闭。

防止旧连接的数据包被收到

一文带你搞定TCP挥手

 

假设没有TIME_WAIT状态,如果有相同的端口的TCP连接被服用后,上图中被延迟SEQ=301的数据包抵达了客户端,客户端是有可能正常接收该报文的,此时就会产生数据错乱现象。

但通过2MSL的等待时间,通信双方的数据包都可以在网络中消失,新的数据包一定是新连接的。

保证连接正确关闭

通过等待2MSL的时间确保最后一次ACK报文被被动断开连接的一方收到,从而正常关闭。

一文带你搞定TCP挥手

 

上图如果服务端没有收到最后一个ACK报文会处于LAST_ACK状态,如果此时客户端发起了一个新的SYN报文请求建立连接,服务端会发送RST报文给客户端,连接建立失败。

但是通过等待2MSL的时间会解决上述问题,因为假设服务端没有收到最后一次ACK报文,会触发超时重传重新发送FIN报文并等待新的ACK报文。客户端在收到新的FIN报文时会重新发送ACK报文并刷新2MSL的计时,最终能够保证服务端的连接能够正常关闭。

TIME_WAIT过多的弊端

服务器如果有TIME_WAIT状态的连接,说明TCP连接的断开是由服务端发起的,此时如果TIME_WAIT的连接过多,将会出现以下问题:

  • 内存资源占用
  • 端口资源占用,假设端口被占满,将无法建立新的连接
# 该参数用于指定开放的端口资源,默认是32768-61000
net.ipv4.ip_local_port_range
一文带你搞定TCP挥手

 

TIME_WAIT优化

TIME_WAIT的优化主要有以下几种方式,每种方式都有利有弊:

  • .NET.ipv4.tcp_tw_reuse和net.ipv4.tcp_timestamps选项
  • net.ipv4.tcp_max_tw_buckets
  • 应用程序使用SO_LINGER,应用强制使用RST关闭

打开net.ipv4.tcp_tw_reuse和net.ipv4.tcp_timestamps选项

参数开启以后,可以复用处于TIME_WAIT的Socket给新的连接使用。

tcp_tw_reuse的功能只能用于连接发起方,开启该参数以后,在调用connect函数时,内核会随机找一个time_wait超过1s的连接给新的连接复用。

net.ipv4.tcp_timestamp默认开启,表示打开对TCP时间戳的支持。时间戳字段存储在TCP头部的选项字段中,用于记录TCP发送方的时间戳和从对端接收到的最新时间戳。

net.ipv4.tcp_max_tw_buckets

当系统中的TIME_WAIT的连接数超过该项的值时,系统那个会将后面TIME_WAIT的连接重置,不推荐使用。

程序使用SO_LINGER

通过设置Sokcet的一些选项,来影响close方法的一些行为。

如果SO_LINGER中的onoff为非0,并且linger为0,调用close方法以后会立即发送一个RST报文给对方,TCP连接会直接跳过四次握手关闭。也过于暴力不推荐。

TCP保活机制

在某个时间段内,如果TCP连接上无任何活动,TCP保活机制开始生效,每隔一段时间就会发送一个探测报文,如果连续几个探测报文都没有收到响应,则认为TCP连接已死,系统内核会将错误信息通知给应用程序。

# 用于控制保活时间,如果7200s内没有活动,则会启动保活机制
net.ipv4.tcp_keepalive_time=7200

# 保活机制每次检测间隔为75s
net.ipv4.tcp_keepalive_intvl=75

# 如果9次探测无响应,则认为对端不可答,中断本次连接
net.ipv4.tcp_keepalive_probes=9

上述三个都是Linux中的默认值,也就是说Linux操作系统中至少经过2小时11分15秒才可以发现一个死亡连接。

Socket编程

public ServerSocket(int port, int backlog) throws IOException {
    this(port, backlog, null);
}

JAVA中的ServerSokcet的初始化方法中有一个backlog参数,该参数在Linux2.2以前代表SYN队列大小,但是在Linux 2.2以后就是全连接队列的大小(accept队列的大小)。

  • 半连接队列(SYN队列):接收SYN请求,处于SYN_RCVD状态的连接
  • 全连接队列(Accept队列):完成三次握手处于ESTABLISHED状态的连接

Socket的一些连接操作对应的tcp连接步骤

一文带你搞定TCP挥手

 

  1. Socket在调用connect方法时,会发送SYN包给服务端,服务端会接收到到SYN报文,并且服务端会半连接队列里初始化一个连接。
  2. 服务端在处理完以后会发送ACK+SYN报文给客户端,客户端收到以后切实是就是connect方法的返回,同时客户端也需要对服务端的SYN报文进行应答。
  3. 服务端收到ACK报文以后,半连接队里的连接会被转移到全连接队列中,此时accept方法会成功拿到连接并生成一个Socket(这个就是传输时的Socket,不是监听Socket)。

close方法对应的TCP四次挥手

一文带你搞定TCP挥手

 

  1. 客户端调用close方法,会发送一个FIN报文给服务端
  2. 服务端收到FIN报文时,TCP协议栈会为该包插入一个文件结束符EOF到接收缓冲区,应用程序可以通过read方法获取到该文件结束符。**EOF会被放在所有的数据之后。**服务端会进入CLOSED_WAIT状态。
  3. 服务端处理完所有的数据以后,会读取到EOF,此时会调用close方法关闭Socket,然后发送一个FIN包进入LAST_ACK状态。
  4. 后面的其实就是TCP最终断开连接。
声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>