[网络基础知识]TCP有限状态机

Posted ouyangshima

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[网络基础知识]TCP有限状态机相关的知识,希望对你有一定的参考价值。

TCP是传输层协议,实现了一种可靠的通信。它从不同角度提供了多种可靠性保障措施来为网络传输提供确定性。连接性就是其中之一,不像UDP的无连接状态,TCP在数据传输之前会进行连接,只有双方都协调完成后,才会进行数据传输;同样的,在结束时,又会断开连接,通告传输的完成;在数据传输过程中,又会对每个传输进行确认。


  1. 粗实箭头表示客户进程的正常迁移
  2. 粗虚线箭头表示对服务器进程的正常迁移
  3. 细线箭头表示异常变迁

TCP 协议的操作可以使用一个具有11 种状态的有限状态机( Finite State Machine )来表示。图中用粗线表示客户端主动和被动的服务器端建立连接的正常过程:客户端的状态变迁用粗实线,服务器端的状态变迁用粗虚线。细线用于不常见的序列,如 复位、同时打开、同时关闭等。图中的每条状态变换线上均标有“事件/动作”:事件是指用户执行了系统调用( CONNECT 、 LISTEN 、 SEND 或 CLOSE )、收到一个报文段( SYN 、 FIN 、 ACK 或 RST )、或者是出现了超过两倍最大的分组生命期的情况;动作是指发送一个报文段( SYN 、 FIN 或 ACK )或什么也没有(用“-”表示)。
每个连接均开始于CLOSED 状态。当一方执行了被动的连接原语( LISTEN )或主动的连接原语( CONNECT )时,它便会脱离 CLOSED 状态。如果此时另一方执行了相对应的连接原语,连接便建立了,并且状态变为 ESTABLISHED 。任何一方均可以首先请求释放连接,当连接被释放后,状态又回到了CLOSED 。

11 种有限状态机

CLOSED:关闭状态,没有连接活动或正在进行 LISTEN:监听状态,服务器正在等待连接进入 SYN RCVD:收到一个连接请求,尚未确认 SYN SENT:已经发出连接请求,等待确认 ESTABLISHED:连接建立,正常数据传输状态 FIN WAIT 1:(主动关闭)已经发送关闭请求,等待确认 FIN WAIT 2:(主动关闭)收到对方关闭确认,等待对方关闭请求 TIMED WAIT:完成双向关闭,等待所有分组死掉 CLOSING:双方同时尝试关闭,等待对方确认 CLOSE WAIT:(被动关闭)收到对方关闭请求,已经确认 LAST ACK:(被动关闭)等待最后一个关闭确认,并等待所有分组死掉

常见的问题

网络服务经常出现连接数不够的问题,用netstat -an来定位,统计所有tcp状态的个数。

大量SYN_RCVD 

如果服务器上出现大量的SYN_RCVD状态的TCP连接说明这些连接一直没有收到ACK包,这主要有两种可能,一种是对方(请求方或客户端)没有收到服务器发送的[SYN,ACK]包,另一种可能是对方收到了[SYN,ACK]包却没有ACK。

对于第一种情况一般是由于网络结构或安全规则限制导致(SYN,ACK)包无法发送到对方,这种情况比较容易判断:只要在服务器上能够ping通互联网的任意主机,基本可以排除这种情况。
对于第二种情况要稍微复杂一些,这种情况还有两种可能:一种是对方根本就不打算ACK,一般在对方程序有意为之才会出现,如SYN Flood类型的DOS/DDOS 攻击;另一种可能是对方收到的[SYN,ACK]包不合法,常见的是SYN包的目的地址(服务地址)和应答[SYN,ACK]包的源地址不同。这种情况在只配置了DNAT而不进行SNAT的服务网络环境下容易出现,主要是由于inbound(SYN包)和outbound([SYN,ACK]包)的包穿越了不同的网关/防火墙/负载均衡器,从而导致[SYN,ACK]路由到互联网的源地址(一般是防火墙的出口地址)与SYN包的目的地址(服务的虚拟IP) 不同,这时客户机无法将SYN包和[SYN,ACK]包关联在一起,从而会认为已发出的SYN包还没有被应答,于是继续等待应答包。这样服务器端的连接一直保持在SYN_RCVD状态(半开连接)直到超时。

大量close_wait

在被动关闭连接情况下,在已经接收到FIN,但是还没有发送自己的FIN的时刻,连接处于CLOSE_WAIT状态。
通常来讲,CLOSE_WAIT状态的持续时间应该很短,正如SYN_RCVD状态。但是在一些特殊情况下,就会出现连接长时间处于CLOSE_WAIT状态的情况。
出现大量close_wait的现象,主要原因是某种情况下对方关闭了socket链接,但是我方忙与读或者写,没有关闭连接。代码需要判断socket,一旦读到0,断开连接,read返回负,检查一下errno,如果不是AGAIN,就断开连接。
解决方法,基本的思想就是要检测出对方已经关闭的socket,然后关闭它。
  1. 代码需要判断socket,一旦read返回0,断开连接,read返回负,检查一下errno,如果不是AGAIN,也断开连接。(注:在UNP 7.5节的图7.6中,可以看到使用select能够检测出对方发送了FIN,再根据这条规则就可以处理CLOSE_WAIT的连接)
  2. 给每一个socket设置一个时间戳last_update,每接收或者是发送成功数据,就用当前时间更新这个时间戳。定期检查所有的时间戳,如果时间戳与当前时间差值超过一定的阈值,就关闭这个socket。
  3. 使用一个Heart-Beat线程,定期向socket发送指定格式的心跳数据包,如果接收到对方的RST报文,说明对方已经关闭了socket,那么我们也关闭这个socket。
  4. 设置SO_KEEPALIVE选项,并修改内核参数

大量last_ack

大量ack主要是服务器等待客户端主动关闭,而客户端没有及时发出最后一个ack,或者在传输过程中丢包了,可以减少keepalive时间,利用keepalive来关闭这些连接。
调整参数:
# 当keepalive打开的情况下,TCP发送keepalive消息的频率。(默认值是7200(2小时),服务器建议设为1800)
net.ipv4.tcp_keepalive_time = 1800
# 在近端丢弃TCP连接之前﹐要进行多少次重试。默认值是7个﹐在大负载服务器上建议调整为3)
net.ipv4.tcp_orphan_retries = 3

大量TIME_waited

TIME_WAIT产生原因:
主要有两个原因:防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失) 可靠的关闭TCP连接,在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。
一般都是客户端主动关闭连接,服务器被动关闭连接,被动关闭连接时不存在time_wait。但是新增了一些其他需求,比如nginx的负载均衡功能,导致服务器本身也成为一个客户端(比如访问php服务器),在短链接情况下,会更频繁地关闭主动关闭连接,导致系统预留大量的timewait状态的socket。服务器申请不到新的socket导致的,客户不能正常访问服务器。
一个通用比较通用的解决办法:
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量
SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 (www.111cn.net)表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout = 30 表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。
net.ipv4.tcp_max_tw_buckets = 5000 表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。
默  认为180000,改为5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于Squid,效果却不大。此项参数可以控制TIME_WAIT套接字的最大数量,避免Squid服务器被大量的TIME_WAIT套接字拖死。
 
从解决问题的思路, 服务器常见的socket不够问题(除established),其他问题一般都可以通过定时监控来提前发现问题,及时修改系统参数来解决问题。或者增加一套,根据系统剩下的socket资源情况来调整tcp参数,快速规避问题。

TCP有限状态机

以上是关于[网络基础知识]TCP有限状态机的主要内容,如果未能解决你的问题,请参考以下文章

tcp有限状态机

TCP 握手和挥手图解(有限状态机)

TCP/IP有限状态机

TCP有限状态机

TCP有限状态机

网络 之 三次握手&四次挥手 介绍