1​ TIME-WAIT 状态

主动关闭连接的一方,在四次挥手最后一次发送 ACK 后,进入 TIME_WAIT 状态。在这个状态里,主动关闭连接一方等待 2MSL(Maximum Segment Life,报文段最大生存时间,在RFC793 中定义为 2 min,而在 Linux 中定义为 30s),若这段时间内未收到被动关闭一方重发的 FIN,则由 TIME_WAIT 状态转到 CLOSED 状态。

祭上状态机图:

在这里为了讨论方便,假设主动关闭连接的一方均为本地客户端,被动关闭连接的一方均为服务端,以客户端与服务端 TCP 状态的变化来讨论。

2​ 存在的目的

为什么 TCP 需要设置 TIME-WAIT 状态等待 2MSL 才能转到 CLOSED 状态关闭连接呢?

​2.1​ 避免在新连接上收到旧连接的数据

避免在同一四元组(源地址、源端口、目的地址、目的端口)上的新连接收到旧连接的数据。
如下图所示,服务端第一次发送的序号为 3 的数据包因延时未送达客户端,服务端重发第二次序号为 3 的数据包后客户端接收到并主动断开连接。

在很短时间内,客户端重新向服务端发起连接,这时服务端发送序号 1、序号 2 的数据给客户端,但同时客户端也收到了在网络上延时到达的服务端第一次发送的序号为 3 的数据包。

RFC793 中描述了 ISN 每 4 微秒会自增 1,达到  2^32 后又从 0 开始。这样周始往复,一个 ISN 的周期大约是 4.55 个小时。所以虽然 TCP 每次建立连接时的 SYN 序列号都不会相同,但若如果在接收窗口很大的情况下,快速重新建立的连接使用的序列号可能会有一部分与旧连接使用过的序列号重合,因此新连接误接收旧连接相同序列号的数据包是有机率发生的。

客户端通过 TIME-WAIT 等待 2MLS 的时间可以避免这个问题:1)收到的延迟数据包被丢弃;2)2MLS 的时间会让 ISN 与旧连接使用过的序列号重合范围减小甚至不重合。TIME-WAIT 为新连接准备了时间缓冲带,旧连接的数据包与新连接的数据包因此有足够的界限。

2.2​ 确保服务端正确关闭连接

服务端如果没有收到四次挥手中的最后一个 ACK,将会一直处于 LAST-ACK 状态,并一直重传 FIN 报文,有三种可能的情况发生:

  • 放弃重传 FIN,并移除该连接;

  • 收到 ACK,状态转为 CLOSED,并正常关闭连接;

  • 收到 RST,并移除该连接。

客户端通过 TIME-WAIT 等待 2MLS 时间确保服务端正确关闭连接。如若在 TIME-WAIT 收到服务端重传的 FIN,说明最后发送的 ACK 在网络中丢失了,需要重发 ACK 以确保服务端能收到 ACK 并正确关闭连接。这也是为什么 TIME-WAIT 等待时间是 2MLS 的原因,如果服务端重传 FIN,客户端必定在 2MLS 期间内收到:即使服务端收到 ACK 再重传 FIN, 这个过程也只需要 2MLS 时间。

3​ 引发的问题

TIME-WAIT 状态虽好,但是当大量的连接处于 TIME-WAIT 状态而未被及时关闭,它会导致以下问题:

​3.1​ 占用连接资源

TIME-WAIT 状态在 Linux 下会持续 60s,在这 60s 内,不能建立相同相同四元组(源地址、源端口、目的地址、目的端口)的新连接。

​3.2​ 占用内存空间

在内核中,一个 TIME-WAIT 状态的 socket 与三个结构体相关,而这些数据结构在内存中都占用一定的空间:

struct tcp_timewait_sock

每当收到一个新的报文时,会在名为  “TCP established” 的哈希表中查找这个连接。该哈希表中的每个桶不仅包含 TIME-WAIT 状态的连接链表,还包含其它正常状态的连接链表。其中,TIME-WAIT 状态的链表元素数据结构是 tcp_timewait_sock(168 bytes),而其它正常状态的链表元素结构是 struct tcp_sock。

struct tcp_timewait_sock {

    struct inet_timewait_sock tw_sk;

    u32    tw_rcv_nxt;

    u32    tw_snd_nxt;

    u32    tw_rcv_wnd;

    u32    tw_ts_offset;

    u32    tw_ts_recent;

    long   tw_ts_recent_stamp;

};

struct inet_timewait_sock {

    struct sock_common  __tw_common;

    int                     tw_timeout;

    volatile unsigned char  tw_substate;

    unsigned char           tw_rcv_wscale;

    __be16 tw_sport;

    unsigned int tw_ipv6only     : 1,

                 tw_transparent  : 1,

                 tw_pad          : 6,

                 tw_tos          : 8,

                 tw_ipv6_offset  : 16;

    unsigned long            tw_ttd;

    struct inet_bind_bucket *tw_tb;

    struct hlist_node        tw_death_node;

};

struct hlist_node

struct inet_timewait_sock 的数据成员 tw_death_node,用来跟踪 TIME-WAIT 状态的连接的存活时间,存活时间越长排在链表越靠后的位置。

struct inet_bind_socket

绑定端口的哈希表,保存本地被绑定的端口及相关联的参数,用于:1)判断是否可以在给定的端口上绑定;2)寻找未被绑定的可用的端口。

哈希表的每个元素数据结构为 inet_bind_socket(48 bytes)。

3.3​ 占用 CPU 资源

在 CPU 使用上,查找一个可用的本地端口的代价可能有一丢丢大。这项工作由函数 inet_csk_get_port() 完成:锁住并迭代本地的所有端口,直到找到一个未使用的端口。

​4​ 解决方案

​4.1​ 增加四元组可选范围

具体来说:

  1. 客户端设置 net.ipv4.ip_local_port_range 来扩充客户端端口范围;

  2. 客户端使用更多的 IP 地址,例如,在负载均衡器上配置更多的 IP;

  3. 服务端监听更多的端口,如 81,82,83 等;

  4. 服务端监听更多的 IP 地址。

​4.2​ SO_LINGER 选项

默认情况下,应用程序调用 close() 关闭 socket 后会立即返回,TCP 模块会把发送缓存中的残余的数据继续发送完,最终转到 TIME-WAIT 状态。

SO_LINGER 是 socket 的一个选项,当 socket 被  close 时,该选项控制 socket 是否延迟关闭,以及如何处理发送缓存中的残余数据。

通过调用 setsockopt 来设置 socket 选项:

#include <sys/socket.h>

int setsockopt( int sockfd, int level, int option_name, const void* option_value, socklen_t option_len );

sockfd 参数指定被操作的目标 socket,level 参数指定要操作哪个协议的选项,比如 IPv4、IPv6、TCP 等,option_name 参数则指定选项的名字。option_value 和 option_len 参数分别是被操作选项的值和长度。详情见 setsockopt

设置 SO_LINGER 选项的值时,我们需要给 setsockopt 传递一个 linger 类型的结构体,其定义如下:

#include <sys/socket.h>

struct linger

{

    int l_onoff; /* 开启:非 0,关闭:0 */

    int l_linger;  /* 延迟时间 */

};

根据 linger 结构体中两个成员变量的不同值,close() 会产生如下三种行为之一:

  • l_onff 为 0:SO_LINGER 关闭,close 用默认行为来关闭 socket;

  • l_onff 非 0:SO_LINGER 开启,

    • l_linger == 0:客户端应用程序调用 close() 后立即返回,发送缓存中的残余数据被丢弃,同时发送一个 RST 给服务端来异常终止当前连接;

    • l_linger > 0:

      • socket 是阻塞的:客户端应用程序调用 close() 后等待 l_linger 的时间,直到发送完所有缓存中的残余数据并得到远端的确认。如果这段时间内还没有发送完残余数据,close() 返回 -1 并设置 errno 为 EWOULDBLOCK;

      • socket 是非阻塞的:客户端应用程序调用 close() 后立即返回,根据 close() 的返回值与 error 来判断残余数据是否已经发送完毕。

因此,开启 SO_LINGER 并将 l_linger 设置为 0 时,服务端会收到 RST 并关闭连接。相当于跳过 TIME_WAIT 状态直接关闭服务端的连接。但是,SO_LINGER 并没有解决新连接收到旧连接数据包的问题。

​4.3​ SO_REUSEADDR 选项/net.ipv4.tcp_tw_reuse

开启 SO_REUSEADDR 选项或者配置 net.ipv4.tcp_tw_reuse 为 1 后,Linux 将可以复用处于 TIME-WAIT 状态的连接。前面我们说到,TIME-WAIT 状态存在的目的一是为了避免在新连接上收到旧连接的数据,二是为了确保被动关闭方正确关闭连接,那么我们开启 SO_REUSEADDR 复用连接不是一切回到原点了吗?这一切都是 TCP 时间戳选项的功劳。

RFC 1323 描述了一套如何在大宽带高速网络下提升性能的 TCP 扩展,在这其中,新定义一个新的 TCP 时间戳选项:

Kind

1 字节,固定为 8。

Length

1 字节,固定为 10。

Timestamp Value (TSval)

4 字节, TCP 发送此选项时的当前时间戳。

TImestamp Echo Reply (TSecr)

4 字节,仅在 ACK 中有效,把收到的 TSval 回填到 TSecr 中发回给远端。当此报文不是 ACK,即 TSercr 无效时,TSecr 的值必须是 0 。

我们来看时间戳如何接手 TIME-WAIT 的问题:

1)避免在新连接上收到旧连接的数据

旧连接的数据会因为时间戳过于老旧而被丢弃;

2)确保服务端正确关闭连接

一旦客户端用新的连接替代了 TIME-WAIT 状态的连接,客户端发出 SYN 报文后服务端重传 FIN 报文(因为时间戳的关系,服务端识别出是新的连接,客户端的 SYN 报文被忽略)。因为客户端当前处于 SYN-SENT 状态,所以会回复 RST,这使得服务端能正确脱离 LAST-ACK 状态并关闭连接。这之后,SYN 初始化报文会重发,重新进入新连接的建立流程:

​4.4​ net.ipv4.tcp_tw_recycle

net.ipv4.tcp_tw_recycle 配置为 1 会开启系统对 TIME-WAIT 状态的 socket 的快速回收。

net.ipv4.tcp_tw_recycle 同样利用 TCP 的时间戳选项来优化 TIME-WAIT:Linux 每收到一个远端(IP)的数据包,都记录它的时间戳。当处于 TIME-WAIT 状态的 socket 收到的同一远端的数据包时间戳小于记录值,Linux 直接丢弃该数据包并回收 socket。

但是,net.ipv4.tcp_tw_recycle 并不被推荐(Linux 从 4.12 内核版本开始移除了 tcp_tw_recycle 配置),它可能会导致很多难以排查的古怪问题。特别是服务器或者客户端在 NAT 网络中,多个服务器或客户端共用 NAT 设备的时间戳,数据包可能会被丢弃。

​4.5​ net.ipv4.tcp_max_tw_buckets

表示系统同时保持 TIME-WAIT 套接字的最大数量,如果超过这个数字,TIME-WAIT 套接字将立刻被清除并打印警告信息。默认值为180000。

​5​ 参考资料

  1. TCP/IP详解 卷1:协议

  2. Linux 高性能服务器编程

  3. TCP 的那些事儿(上)

  4. Coping with the TCP TIME-WAIT state on busy Linux servers …

  5. 对 Linux TCP 的若干终点和误会

  6. 被抛弃的tcp_recycle

深入探索 TCP TIME-WAIT的更多相关文章

  1. TCP/IP详解 卷一(第七、八章 Ping、Traceroute程序)

    Ping程序 Ping程序由Mike Muuss编写,目的是为了测试另一台主机是否可达. 该程序发送一份ICMP回显请求报文给主机,并等待返回ICMP回显应答. ping程序还能测出到这台主机的往返时 ...

  2. TCP/IP||Traceroute

    1.概述 由Van jacobson编写的工具,用于探索tcp/ip协议,使用ICMP报文和首部TTL字段,TTL字段由发送端设置一个8bit字段,初始值为RFC指定,当前值为64, 每个处理数据的路 ...

  3. Web性能优化之-深入理解TCP Socket

    什么是Socket?    大家都用电脑上网,当我们访问运维社区https://www.unixhot.com的时候,我们的电脑和运维社区的服务器就会创建一条Socket,我们称之为网络套接字.那么既 ...

  4. 运维都该会的Socket知识!

    本篇博客转自赵班长 所有运维都会的Socket知识!!! 原创: 赵班长 新运维社区 什么是Socket? 大家都用电脑上网,当我们访问运维社区https://www.unixhot.com的时候,我 ...

  5. TCP点对点穿透探索--失败

    TCP点对点穿透探索 点对点穿透是穿透什么 点对点穿透,需要实现的是对NAT的穿透.想实现NAT的穿透,当然要先了解NAT到底是什么,以及NAT是用来干什么的.NAT全称Network Address ...

  6. 【原创】TCP超时重传机制探索

    TCP超时重传机制探索 作者:tll (360电商技术) 1)通信模型 TCP(Transmission Control Protocol)是一种可靠传输协议.在传输过程中当发送方(sender)向接 ...

  7. 从零探索Java网络编程01之 TCP/IP 与 Socket

    最近完成了几项比较简单的项目, 终于是在996里偷了点闲暇时光, 想着来研究研究些啥吧?  一个普通的控制台日志映入了我的眼帘(孽缘呀): (图中使用 SpringBoot 的 log4j 来输出日志 ...

  8. .Net TCP探索(一)——TCP服务端开发(同时监听多个客户端请求)

        最近在园子里看了大神写的(面试官,不要再问我三次握手和四次挥手),忍不住写段程序来测试一番.     在网上找了很多例子,大多只实现了TCP点对点通讯,但实际应用中,一个服务器端口往往要监听多 ...

  9. ASP.Net请求处理机制初步探索之旅 - Part 1 前奏

    开篇:ASP.Net是一项动态网页开发技术,在历史发展的长河中WebForm曾一时成为了ASP.Net的代名词,而ASP.Net MVC的出现让这项技术更加唤发朝气.但是,不管是ASP.Net Web ...

随机推荐

  1. HihoCode-1323-回文字符串

    参考博客: https://blog.csdn.net/mitsuha_/article/details/76690634 https://blog.csdn.net/u014142379/artic ...

  2. Filter 中注入失败问题

    参考: https://www.cnblogs.com/digdeep/p/4770004.html?tvd https://www.cnblogs.com/EasonJim/p/7666009.ht ...

  3. 吴裕雄--天生自然 R语言开发学习:高级编程

    运行的条件是一元逻辑向量(TRUE或FALSE)并且不能有缺失(NA).else部分是可选的.如果 仅有一个语句,花括号也是可以省略的. 下面的代码片段是一个例子: plot(x, y) } else ...

  4. DEBUG -- CLOSE BY CLIENT STACK TRACE问题的两种解决方案,整理自网络

    1.DEBUG -- CLOSE BY CLIENT STACK TRACE 最近用c3p0遇到各种奇怪的问题,也不知道是它不行还是我不行. 今天又遇到了一个"DEBUG -- CLOSE ...

  5. 录音文件lame转换MP3相关配置

    文件下载整个功能完成了,那么对应的文件上传也跑不了.So~ Look here~ 业务需求是录制音频然后上传到七牛并且Android可以读. 与安卓沟通了一下统一了mp3格式,大小质量都不错.由于AV ...

  6. 陪诊App,陪孝子找回人性

    2015年开始,越来越多的陪诊App开始上线,纷纷迎来了自己的第一批用户,同时,也迎来了大量资本的青睐,有些名气的问诊app甚至能单轮获得超过1000万元的融资,事实上,投资者是否青睐,简直就是中国新 ...

  7. DZNEmptyDataSet的使用

    DZNEmptyDataSet是外国友人写的开源项目,github地址(具体的使用以及Demo,点击进入github主页),简单介绍下DZNEmptyDataSet的使用方法. 对于iOS开发者来说, ...

  8. React Native Build Apk

    1 React Native安卓项目打包APK 1.1 产生签名的key 先通过keytool生成key 1 keytool -genkey -v -keystore demo-release-key ...

  9. 再谈拍照,OPPO这次拿什么和iPhone7拼?

    ​一年一度的iPhone新机如期而至,双摄像头成为iPhone 7 Plus标配,尽管在这之前,双摄像头已有少数厂商在手机上装备,但苹果一出,市场必定全面跟进.无论各大厂商是否采用双摄像头,在手机拍照 ...

  10. 量化投资学习笔记29——《Python机器学习应用》课程笔记03

    聚类的实际应用,图像分割. 利用图像的特征将图像分割为多个不相重叠的区域. 常用的方法有阈值分割,边缘分割,直方图法,特定理论(基于聚类,小波分析等). 实例:利用k-means聚类算法对图像像素点颜 ...