TCP的11种状态

  • CLOSED(关闭):初始状态,表示连接未建立或已关闭。
  • LISTEN(监听):表示服务器正在等待客户端请求连接。
  • SYN-SENT(同步已发送):表示客户端已经发送了连接请求报文段,并等待服务器确认。
  • SYN-RCVD(SYN-RECEIVED,同步已接收):表示服务器已经接收到了连接请求报文段,并向客户端发送了确认报文段。
  • ESTABLISHED(已建立):表示连接已经建立,双方可以进行数据传输。
  • FIN-WAIT-1(终止等待1):表示客户端已经发送了关闭连接请求报文段,等待服务器确认或拒绝。
  • FIN-WAIT-2(终止等待2):表示客户端已经收到了服务器的确认报文段,等待服务器发送关闭连接请求报文段。
  • CLOSE-WAIT(关闭等待):表示服务器已经发送了关闭连接请求报文段,等待客户端确认或拒绝。
  • CLOSING(关闭中):表示客户端和服务器同时发送了关闭连接请求报文段,等待对方确认或拒绝。
  • LAST-ACK(最后确认):表示服务器已经收到了客户端的关闭连接请求报文段,并发送了确认报文段,等待客户端确认或拒绝。
  • TIME-WAIT(时间等待):表示连接已经关闭,但是为了保证最后的数据传输已经完成,客户端和服务器都需要等待一段时间。


一般比较以下这三种TCP状态:

SYN_RECV (三次握手阶段出现)


服务端收到建立连接的SYN没有收到ACK包的时候处在SYN_RECV状态。

有两个相关系统配置:


1,net.ipv4.tcp_synack_retries :INTEGER

默认值是5

对于远端的连接请求SYN,内核会发送SYN + ACK数据报,以确认收到上一个 SYN连接请求包。这是所谓的三次握手( threeway handshake)机制的第二个步骤。这里决定内核在放弃连接之前所送出的 SYN+ACK 数目。不应该大于255,默认值是5,对应于180秒左右时间。通常我们不对这个值进行修改,因为我们希望TCP连接不要因为偶尔的丢包而无法建立。


2,net.ipv4.tcp_syncookies


一般服务器都会设置net.ipv4.tcp_syncookies=1来防止SYN Flood攻击。假设一个用户向服务器发送了SYN报文后突然死机或掉线,那么服务器在发出SYN+ACK应答报文后是无法收到客户端的ACK报文的(第三次握手无法完成),这种情况下服务器端一般会重试(再次发送SYN+ACK给客户端)并等待一段时间后丢弃这个未完成的连接,这段时间的长度我们称为SYN Timeout,一般来说这个时间是分钟的数量级(大约为30秒-2分钟)。

这些处在SYNC_RECV的TCP连接称为半连接,并存储在内核的半连接队列中,在内核收到对端发送的ack包时会查找半连接队列,并将符合的requst_sock信息存储到完成三次握手的连接的队列中,然后删除此半连接。大量SYNC_RECV的TCP连接会导致半连接队列溢出,这样后续的连接建立请求会被内核直接丢弃,这就是SYN Flood攻击。

能够有效防范SYN Flood攻击的手段之一,就是SYN Cookie。SYN Cookie原理由D. J. BernstainEric Schenk发明。SYN Cookie是对TCP服务器端的三次握手协议作一些修改,专门用来防范SYN Flood攻击的一种手段。它的原理是,在TCP服务器收到TCP SYN包并返回TCP SYN+ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值。在收到TCP ACK包时,TCP服务器在根据那个cookie值检查这个TCP ACK包的合法性。如果合法,再分配专门的数据区进行处理未来的TCP连接。

服务器上SYN_RECV连接个数为上千个到1万,对于一个高并发连接的服务器,这个范围比较正常。


可用来进行SYN洪泛攻击




CLOSE_WAIT (四次挥手阶段出现)


发起TCP连接关闭的一方称为client,被动关闭的一方称为server。被动关闭的server收到FIN后,但未发出ACK的TCP状态是CLOSE_WAIT。出现这种状况一般都是由于server端代码的问题,如果你的服务器上出现大量CLOSE_WAIT,应该要考虑检查代码。

被动关闭一方有CLOSE_WAIT,如果过度,可能是代码问题




TIME_WAIT (四次挥手阶段出现)


根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方 socket将进入TIME_WAIT状态。

TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),在Windows下默认为4分钟,即240秒。

TIME_WAIT状态下的socket不能被回收使用。具体现象是对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接,将导致服务器端存在大量的处于TIME_WAIT状态的socket, 甚至比处于Established状态下的socket多的多,严重影响服务器的处理能力,甚至耗尽可用的socket(主要是耗端口),停止服务。 (socket、端口、进程的关系)

主动关闭一方有TIME_WAIT,会持续2MSL的时长。如果过多,会耗尽socket,需要特别注意!


为什么需要TIME_WAIT?TIME_WAIT是TCP协议用以保证被重新分配的socket不会受到之前残留的延迟重发报文影响的机制,是必要的逻辑保证。

TCP协议的实现中,需要确保在网络中所有的数据包都被正确地接收和处理完毕,才能最终关闭连接,以避免可能出现的数据包丢失和重复等问题


和TIME_WAIT状态有关的系统参数有一般由3个,一般修改设置如下:

1
2
3
4
5
net.ipv4.tcp_tw_recycle = 1  # 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_tw_reuse = 1 # 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_fin_timeout = 30 # 默认60s,减小fin_timeout,减少TIME_WAIT连接数量。

tcp中11种状态详解



番外:

性能测试018:到底该不该开启 tcp_tw_reuse

让我们探讨一下Linux上的tcp_tw_reuse设置项。这个设置涉及到TIME_WAIT状态的连接,即处于等待状态的连接,其目的是管理这些状态下连接的重用策略。

左耳朵耗子,即陈皓,在极客时间上开设的专栏中提到,很多中文文档推荐开启这两个参数,但细查资料会发现,开启这些参数并非总是推荐的做法。我个人在Ubuntu 20.04上测试tcp_tw_reuse时,发现其默认值为disabled,意即在默认情况下是关闭的。Linux文档说明,如果从协议的角度看是安全的,那么新连接可以重用TIME_WAIT状态的套接字,但通常不建议未经专家建议或要求修改此设置。

在实际操作中,我查询到的默认值却是2,而非文档中提及的0或1,这引发了我的好奇。进一步查阅Linux内核的提交记录,我发现tcp_tw_reuse的取值已从布尔类型扩展为整型,现在有三个可能的值:0、1和2。其中,0和1的含义保持不变,而2则允许在特定条件下重用TIME_WAIT状态的套接字,特别是针对本机通信地址如127.0.0.1localhost

技术专家邵雅芳在极客时间的专栏中提出,不开启此选项可能导致在快速启动应用程序时遇到端口占用问题,因此建议将tcp_tw_reuse设置为1。

综上所述,虽然官方文档默认推荐的设置是0(关闭),而实际默认值可能为2,专家建议在某些情况下开启此功能(设置为1)。那么,面对这些不同的建议,我们应该如何选择呢?

我的方法是进行实际测试。我在一个装有Rocky Linux 9的虚拟机上,运行了Tomcat服务器,并对tcp_tw_reuse的三种设置值进行了压力测试。测试结果显示,无论tcp_tw_reuse设置为0、1还是2,性能差异几乎可以忽略不计。这表明,在我的测试环境下,tcp_tw_reuse的设置对性能的影响微乎其微。

然而,需要指出的是,我的测试环境相对简单,仅限于两台电脑和最高9000 QPS的测试范围。在不同的环境和更高的流量下,tcp_tw_reuse的影响可能会有所不同。此外,虽然我测试的是tcp_tw_reuse对性能的影响,但专家强调的是其在快速启动应用程序时的潜在好处,因此,测试的侧重点存在差异。

总结来说,虽然tcp_tw_reuse在某些情况下可能有其用处,但基于我的测试结果,其对性能的影响不大。在考虑是否修改此设置时,建议根据具体的应用场景和专家建议进行决策。