当前位置: 代码迷 >> 综合 >> TCP连接状态分析:SYNC_RECV,CLOSE_WAIT,TIME_WAIT
  详细解决方案

TCP连接状态分析:SYNC_RECV,CLOSE_WAIT,TIME_WAIT

热度:15   发布时间:2023-12-12 02:55:41.0

转:http://maoyidao.iteye.com/blog/1744277

 

面试时看到应聘者简历中写精通网络,TCP编程,我常问一个问题,TCP建立连接需要几次握手?95%以上的应聘者都能答对是3次。问TCP断开连接需要几次握手,70%的应聘者能答对是4次通讯。再问CLOSE_WAIT,TIME_WAIT是什么状态,怎么产生的,对服务有什么影响,如何消除?有一部分同学就回答不上来。不是我扣细节,而是在通讯为主的前端服务器上,必须有能力处理各种TCP状态。比如统计在本厂的一台前端机上高峰时间TCP连接的情况,统计命令:

 

 

Linux shell代码  
  1. netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'  

 

 

结果:

 

除了ESTABLISHED,可以看到连接数比较多的几个状态是:FIN_WAIT1, TIME_WAIT, CLOSE_WAIT, SYN_RECV和LAST_ACK;下面的文章就这几个状态的产生条件、对系统的影响以及处理方式进行简单描述。

 

TCP状态

TCP状态如下图所示:

可能有点眼花缭乱?再看看这个时序图


 

下面看下大家一般比较关心的三种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. Bernstain和 Eric Schenk发明。SYN Cookie是对TCP服务器端的三次握手协议作一些修改,专门用来防范SYN Flood攻击的一种手段。它的原理是,在TCP服务器收到TCP SYN包并返回TCP SYN+ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值。在收到TCP ACK包时,TCP服务器在根据那个cookie值检查这个TCP ACK包的合法性。如果合法,再分配专门的数据区进行处理未来的TCP连接。

 

观测服务上SYN_RECV连接个数为:7314,对于一个高并发连接的通讯服务器,这个数字比较正常。

CLOSE_WAIT

发起TCP连接关闭的一方称为client,被动关闭的一方称为server。被动关闭的server收到FIN后,但未发出ACK的TCP状态是CLOSE_WAIT。出现这种状况一般都是由于server端代码的问题,如果你的服务器上出现大量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,停止服务。

 

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

 

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

net.ipv4.tcp_tw_recycle = 1

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_fin_timeout = 30

 

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

 

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

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

 

(二)

 

Linux系统资源限制

1. 最大文件数

查看进程允许打开的最大文件句柄数:ulimit -n

查看进程所占的文件描述符: lsof -p xxx | wc -l

设置进程能打开的最大文件句柄数:ulimit -n xxx 

 

2. ulimit -n vs. file-max ?

简单的说, ulimit -n控制进程级别能够打开的文件句柄的数量, 而max-file表示系统级别的能够打开的文件句柄的数量。 

 

ulimit -n的设置在重启机器后会丢失,因此需要修改limits.conf的限制,limits.conf中有两个值soft和hard,soft代表只警告,hard代表真正的限制

 

Cat /etc/security/limits.conf代码  
  1. *               soft    nofile          150000  
  2. *               hard    nofile          150000  

这里我们把soft和hard设置成一样的。 

 

 

“cat /proc/sys/fs/file-max”,或“sysctl -a | grep fs.file-max”查看系统能打开的最大文件数。查看和设置例如:

 

Shell代码  
  1. [root@vm014601 ~]# sysctl -a |grep fs.file-max  
  2. fs.file-max = 200592  
  3. [root@vm014601 ~]# echo "fs.file-max = 2005920" >> /etc/sysctl.conf   
  4. [root@vm014601 ~]# sysctl -p  
  5. [root@vm014601 ~]# cat /proc/sys/fs/file-max                          
  6. 2005920  

file-nr是只读文件,第一个数代表了目前分配的文件句柄数;第二个数代表了系统分配的最大文件句柄数;比如线上系统查看结果:

 

Shell代码  
  1. # cat /proc/sys/fs/file-max  
  2. 1106537  
  3. # cat /proc/sys/fs/file-nr       
  4. 1088  0       1106537  
  5. # lsof | wc -l  
  6. 1506  

可以看到file-nr和lsof的值不是很一致,但是数量级一致。为什么会不一致?原因如下:

 

写道
lsof是列出系统所占用的资源,但是这些资源不一定会占用打开文件号的.
比如共享内存,信号量,消息队列,内存映射.等,虽然占用了这些资源,但不占用打开文件号.

 

我曾经在前端机上很长时间都无法得到lsof | wc -l 的结果,这个时候可以通过file-nr粗略的估算一下打开的文件句柄数。

3. sysckernel.threads-max

指定了内核所能使用的线程(所有进程打开线程之和)的最大数目,通过命令 “cat /proc/sys/kernel/threads-max” 查看当前值。查看和设置例如:

 

Shell代码  
  1. sysctl -a | grep threads  
  2. vm.nr_pdflush_threads = 2  
  3. kernel.threads-max = 229376  

 

本厂系统配置允许打开的线程数 > 229k

 

如果此值设小了会导致:-bash: fork: Resource temporarily unavailable

 

4. 为什么有限制?

为什么Linux内核对文件句柄数、线程和进程的最大打开数进行了限制?以及如果我们把它调的太大,会产生什么样的后果?

 

原因1 - 资源问题:the operating system needs memory to manage each open file, and memory is a limited resource - especially on embedded systems.

原因2 - 安全问题:if there were no limits, a userland software would be able to create files endlessly until the server goes down.

 

最主要的是资源问题,为防止某一单一进程打开过多文件描述符而耗尽系统资源,对进程打开文件数做了限制。

 

5. 设置成多少比较合适?

网上有朋友给了估算公式:file-max number = RAM size/10k;

I am not a kernel expert, but as far as I can see, the default for file-max seems to be RAM size divided by 10k. As the real memory used per file handler should be much smaller (size of struct file plus some driver dependent memory), this seems a quite conservative limit. – jofel Apr 19 at 16:43

 

那么一个12G RAM 的前端机可以打开接近1M的文件。真的可以打开1百万个文件吗?

为了试验,基于MINA写了一个NIO服务,在一个服务上创建了50万活跃率约为1%的TCP连接。观察内存使用 < 4.5G,可以粗略认为单个socket连接使用内存小于10K。因此可以用上面的公式来简单估算。

 

6. Orphan socket

不属于任何进程的socket叫orphan socket。这里顺便一下讨论orphan socket,因为很多网络资源不足导致的错误都和“孤儿socket”有关。

 

6.1 Orphan socket是怎么产生的呢?

网上没有明确的说明,我们做一个线上调查:

 

Shell代码  
  1. [maoyidao@03701 ~]# netstat -nap | awk '/^tcp/  {++S[$NF]} END {for(a in S) print a, S[a]}'  
  2. 2976/sshd 1  
  3. 11065/gearman 1  
  4. - 2166  
  5. 32726/java 31455  
  6. 25554/scribed 4  
  7.   
  8. [maoyidao@03701 ~]# netstat -nap | awk '/^tcp/ {if($NF == "-") {++S[$6]}}  END {for(a in S) print a, S[a]}'  
  9. TIME_WAIT 451  
  10. FIN_WAIT1 655  
  11. ESTABLISHED 118  
  12. FIN_WAIT2 102  
  13. SYN_RECV 249  
  14. CLOSING 2  
  15. LAST_ACK 619  

 

可以看到任何一个TCP stat情况下都有可能产生“orphan socket”,但多数是在建立过程当中,以及断开连接中的socket。

 

可以通过以下参数减少orphan socket的产生。

sysctl -a | grep orphan

net.ipv4.tcp_orphan_retries = 0

net.ipv4.tcp_max_orphans = 65536

 

6.2 Orphan socket要消耗多少系统资源?

网上的说法是一个orphan socket需要占用64K内存,查到该说法的出路来自这里:http://www.kernel.org/doc/man-pages/online/pages/man7/tcp.7.html

tcp_max_orphans (integer; default: see below; since Linux 2.4)

              The maximum number of orphaned (not attached to any user file handle)

              TCP sockets allowed in the system.  When this number is exceeded, the

              orphaned connection is reset and a warning is printed.  This limit

              exists only to prevent simple denial-of-service attacks.  Lowering this

              limit is not recommended.  Network conditions might require you to

              increase the number of orphans allowed, but note that each orphan can

              eat up to ~64K of unswappable memory.  The default initial value is set

              equal to the kernel parameter NR_FILE.  This initial default is

              adjusted depending on the memory in the system.

 

难道Orphan Socket占用的资源比普通socket多?maoyidao还没有来得及试验,但从理论上讲,占用资源多少和是否是orphan socket是无关的。

 

7. Out of socket memory

谈到Orphan Socket就需要提一下“Out of socket memory”错误。网上已有很清晰的说明:http://jaseywang.me/2012/05/09/%E5%85%B3%E4%BA%8E-out-of-socket-memory-%E7%9A%84%E8%A7%A3%E9%87%8A-2/

摘录一段:

cat /proc/sys/net/ipv4/tcp_mem

69618   92825   139236

第一个数字表示,当 tcp 使用的 page 少于 69618 时,kernel 不对其进行任何的干预

第二个数字表示,当 tcp 使用了超过 92825 的 pages 时,kernel 会进入 “memory pressure”

第三个数字表示,当 tcp 使用的 pages 超过 139236 时,我们就会看到题目中显示的信息

 

maoyidao注:tcp_mem里的数字是page数,转换成字节需要看下系统的page size有多大:

getconf PAGESIZE

4096

 

看看本厂的设置:

cat /proc/sys/net/ipv4/tcp_mem

196608  262144  393216

 

可以看到我们设置的比较大,这是针对前端机的一些优化。再看一下系统当前的情况

[maoyidao@03701 ~]# cat /proc/net/sockstat

sockets: used 30755

TCP: inuse 31684 orphan 1166 tw 600 alloc 31866 mem 5838

UDP: inuse 4 mem 0

RAW: inuse 0

FRAG: inuse 0 memory 0

 

(三)

 

 

上篇介绍了和系统设置有关的几个参数,在一台系统上,连接到一个服务时的本地端口是有限的。由于端口是16位整数,也就只能是0到65535,而0到1023是预留端口,所以能分配的只是 1024到65534,也就是64511个。也就是说,一台机器只能创建六万多个长连接。查看本机端口设置:

maoyidao 写道
sysctl -a | grep port_range
net.ipv4.ip_local_port_range = 1024 65000

/etc/sysctl.conf配置

1. syncookies

net.ipv4.tcp_syncookies = 1

 防止 SYN Flooding攻击,详见:http://maoyidao.iteye.com/admin/blogs/1744277

2. syn-ack

#net.ipv4.tcp_synack_retries=
# net.ipv4.tcp_syn_retries=

maoyidao注:syn-ack握手状态重试次数,默认5,网上有朋友说可改为1或2;通常我的建议是如果没有试验证明在特定场景下明显优势,不要修改默认值,因此本场此参数未指定

3. 内核消息/内存管理设置

kernel.msgmnb = 65536

kernel.msgmax = 65536

 

指定内核中消息队列中消息的最大值(msgmax=64k),default:8192

所有在消息队列中的消息总和的最大值(msgmnb=64k),默认值:16384

即从一个进程发送到另一个进程的消息的最大长度(bytes),进程间的消息传递是在内核的内存中进行的,不会交换到磁盘上,所以如果增加该值,则将增加操作系统所使用的内存数量。

 

maoyidao注:增大消息队列内存值,提高IPC效率

 

kernel.shmmax = 68719476736

Shmmax 是核心参数中最重要的参数之一,用于定义单个共享内存段的最大值,shmmax 设置应该足够大,能在一个共享内存段下容纳下整个的SGA ,设置的过低可能会导致需要创建多个共享内存段,这样可能导致系统性能的下降 。32bit系统中可以设置为系统可支撑的最大值:4G;本系统中设置为64G(这个值实际上超过了前端机的最大内存12G),内存型服务器,比如96G,或128G服务器应该设置的更大。

 

kernel.shmall = 4294967296

含义:系统中共享内存页总

getconf PAGESIZE

4096

total bytes = 4294967296 * 4096 = 16G

4. 网络设置

# 三个整数的向量: min, default, max

net.ipv4.tcp_rmem = 8192 87380 8738000

# min = 4k (same as pagesize), default = 64k, max = 6.25m

net.ipv4.tcp_wmem = 4096 65536 6553600

#

#

net.core.rmem_max = 16777216

net.core.wmem_max = 16777216

 

以上参数的配置可以参考:

 

http://fasterdata.es.net/host-tuning/linux/ 写道
# increase TCP max buffer size settable using setsockopt()
net.core.rmem_max = 16777216 
net.core.wmem_max = 16777216 
# increase Linux autotuning TCP buffer limit 
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

 

# 安全策略

net.ipv4.conf.all.accept_redirects = 0

net.ipv4.conf.default.accept_redirects = 0

net.ipv4.conf.all.send_redirects = 0

net.ipv4.conf.default.send_redirects = 0

net.ipv4.conf.default.arp_ignore = 1

net.ipv4.conf.default.arp_announce = 2

net.ipv4.conf.all.arp_ignore = 1

net.ipv4.conf.all.arp_announce = 2

net.ipv4.ip_no_pmtu_disc = 1

 

# 含义:当启用keepalive的时候,TCP发送keepalive消息的频度

net.ipv4.tcp_keepalive_time = 1800

 

# Controls IP packet forwarding

# 作为前端服务器,禁制IP转发,这个没什么好说的;

net.ipv4.ip_forward = 0

 

# Controls source route verification

# 开启反向路径过滤

net.ipv4.conf.default.rp_filter = 1

 

# 含义:记录的那些尚未收到客户端确认信息的链接请求的最大值。 默认值为4096 如果设置这个值大于4096最好同时调

# 整include/net/tcp.h中的TCP_SYNQ_HSIZE,保证TCP_SYNQ_HSIZE*16 小于或等于tcp_max_syn_backlo,

# 然后重新编译内核。

net.ipv4.tcp_max_syn_backlog = 8192