TCP状态机

        和TCP打交道的时间还是比较多,抓抓包,看看连接,排查一下服务器性能等。以前在学校学习的时候,从课本中学到TCP,但是没有真实的感受,而以前准备面试的时候,大多数时候就是死记硬背的记一下三次握手、四次挥手等知识。感觉学知识还是要学得透彻一点,所以今天也来剖析剖析TCP的各个状态。

来一张经典的图来镇楼


        要直接记住这幅图不容易,因为涉及的状态很多,要一个一个的去理解,然后再串联起来,融会贯通。上图中,粗红色的线代表客户端的TCP连接过程(一般认为主动发起tcp连接的一方为客户端),蓝色虚线是服务器端的TCP连接过程(一般认为被动连接的一方称之为服务器端),细实线用来表示交错的状态。接下来把这些状态分开来理解。

共同状态:

CLOSED:关闭状态,这个最好理解,啥都不干

ESTABLISHED:连接建立状态,此时客户端和服务器端正在进行通信

客户端:

SYN_SENT:当处于关闭状态的客户端准备向服务器发起连接请求时,先发送一个SYN报文到服务器,告诉服务器端,我准备来连接你了,请回答;

FIN_WAIT1:客户端获取了想要的数据了,这个时候是时候关闭连接了,在ESTABLISHED状态下,发送一个FIN报文给服务器端,此时等待服务器端的应答;

FIN_WAIT2:在FIN_WAIT1的状态下,服务器回了一个ACK包给客户端,意思是你发的连接断开我收到了,不过我现在还有数据没发送完,你再等下;

CLOSING:服务器端和客户端都在同时关闭这个连接了,此时客户端只收到一个FIN报文,此时只需要发送一个ACK就行了

TIME_WAIT:服务器发送完数据了,此时就给客户端发送一个FIN保温,意思是现在我这边的内容也发送完了,我们可以正式关闭连接了。此时客户端会发送一个ACK报文给服务器端,告诉服务器端,我收到关闭连接,现在可以确认关闭了,不过我害怕网络有问题,你收不到我发的ACK包,并且我要确定所有的数据都发送完了,所以我需要等一等,确认你确实收到,否则我就要做重传; 

从图中看出,主动打开的一方的状态转换更为复杂,主要有以下三个分支,现在来逐一的解释一下

FIN_WAIT1-->FIN_WAIT2-->TIME_WAIT:这个状态是客户端连接的一般状态。

FIN_WAIT1-->CLOSING-->TIME_WAIT:客户服务器同时关闭了连接

FIN_WAIT1-->TIME_WAIT:服务器端也没内容发了,发送FIN报文到客户端。进入关闭前的最后状态

服务器端:

SYN_RCVD:收到客户端的连接请求,然后发送SYN,并确认客户端的连接请求,即发送ACK包

CLOSING_WAIT:客户端请求关闭,收到FIN报文,并告诉客户端收到关闭请求了,即发送ACK包;

LAST_ACK:数据也发完了,这个时候就开始正式进入关闭状态了。

通过上面得描述,我们可以来梳理下几个常见的问题场景了。

1 为啥建立连接是三次握手,断开连接是四次挥手?

        建立连接,是客户端发起的,而此时连接的双方是没有数据交换的,所以服务器端在应答请求的时候,SYN可以ACK包一起发送到客户端,这也是TCP的常见做法啦。而四次挥手,TCP是全双工通信,客户端单方面的发起断开请求,不代表服务器端马上就得关闭连接,服务器端如果还有数据发送,这时就必须等服务器发送完数据才能最后关闭,因此FIN与ACK的一般情况下不会一起发。

2 TIME_WAIT状态过多咋办?

        系统中TIME_WAIT的连接数很多,会导致什么问题呢?这要分别针对客户端和服务器端来看的。

        首先,如果是客户端发起了连接,传输完数据然后主动关闭了连接,这时这个连接在客户端就会处于TIMEWAIT状态,同时占用了一个本地端口。如果客户端使用短连接请求服务端的资源或者服务,客户端上将有大量的连接处于TIMEWAIT状态,占用大量的本地端口。最坏的情况就是,本地端口都被用光了,这时将无法再建立新的连接。

        针对这种情况,对应的解决办法有2个: 
        1. 使用长连接,如果是http,可以使用keepalive 
        2. 增加本地端口可用的范围,比如Linux中调整内核参数:net.ipv4.ip_local_port_range

        对于服务器而已,由于服务器是被动等待客户端建立连接的,因此即使服务器端有很多TIME_WAIT状态的连接,也不存在本地端口耗尽的问题。大量的TIME_WAIT的连接会导致如下问题: 
    1. 内存占用:因为每一个TCP连接都会有占用一些内存。 

    2. 在某些Linux版本上可能导致性能问题,因为数据包到达服务器的时候,内核需要知道数据包是属于哪个TCP连接的,在某些Linux版本上可能会遍历所有的TCP连接,所以大量TIME_WAIT的连接将导致性能问题。不过,现在的内核都对此进行了优化(待确认)。

最后,再炒点冷饭,我没自己调过,只是从博客看来的

简单总结一下我对于TIME_WAIT状态TCP连接的理解和处理方法: 
1. TIME_WAIT状态的设计初衷是为了保护我们的。服务端不必担心系统中有几w个处于TIME_WAIT状态的TCP连接。可以调大net.ipv4.tcp_max_tw_buckets这个参数。 
2. 使用短连接的客户端,需要关注TIME_WAIT状态的TCP连接,建议是采用长连接,同时调节参数net.ipv4.ip_local_port_range,增加本地可用端口的范围。 
3. 可以开启net.ipv4.tcp_tw_reuse这个参数,但是实际用处有限。 
4. 不要开启net.ipv4.tcp_tw_recycle这个参数,它带来的问题比用处大。 
5. 某些Linux的发行版可以调节TIME_WAIT到CLOSED的等待时间(比如Ali的Linux内核提供了参数net.ipv4.tcp_tw_timeout 
),可以稍微调小一点这个参数。

小弟水平有限,欢迎大家批评指正。

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值