TCP状态机
过儿断的是右手 2018-06-13 21:42:16 1203 收藏 2
展开
和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
),可以稍微调小一点这个参数。
小弟水平有限,欢迎大家批评指正。
————————————————
版权声明:本文为CSDN博主「过儿断的是右手」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/achenwenping/java/article/details/80627599