TCP连接和 time_wait、close_waite
tags:time_wait close_waite RST TCP
引言:前两天朋友公司的服务器垮掉了,最后查出的原因是发现大量的time_wait网络状态。被问起来time_wait是什么,当时就简单的给解释了两句,后来想想正好博客没有特别好的话题,拿来写一下也很不错。
简单的描述产生原因
因为本文较长,如果没有耐心的可以简单了解一下,有耐心的请阅读全文。
TCP是面向连接的,即使不知道具体的过程,也都知道TCP的三次握手,四次挥手。挥手也就是关闭连接,关闭连接的时候,主动关闭的一方在接收到被动关闭方的回应前,处于time_wait状态,并保持一段时间。close_wait是被动关闭方接收到关闭链接请求后所处的状态。
查看状态的代码:
$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
>结果
$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
CLOSE_WAIT 134
ESTABLISHED 245 //已经建立连接,正在传输状态
TIME_WAIT 3
TCP的三次握手和四次挥手的状态转换
要说清楚这个状态,需要了解一定的TCP知识,我们在这里简单介绍一下
三次握手建立连接
TCP必须经过三次握手来建立可靠连接才能彼此传输数数据。假设请求方为客户端与服务端建立TCP连接
在握手前,TCP服务器进程已经创建传输控制块TCB(传输控制模块,记录TCP
运行过程中的变量),准备接受客户连接请求,此时服务器进入LISTEN(监听)状态;
第一次握手:TCP客户端先创建传输控制块TCB,然后向服务器发出连接请求报文,这时报文首部中的同步位SYN=1,初始序列号 seq=x ,TCP客户端进程进入了SYN_SENT(同步已发送)状态。TCP协议中SYN报文段(SYN=1的报文段)不携带数据,但需要消耗掉一个序号(序号每次报文要递增)。
第二次握手:服务端在收到客户端发过来的SYN请求报文后,如果同意连接,则发出确认报文。确认报文ACK=1,SYN=1,确认号是ack=x+1(这个ack是确认号,大写的ACK是标志位,文章最末尾有各个标志位的简单介绍),同时自己也初始化一个序列号 seq=y,此时,服务器TCP进程进入了SYN_RCVD(收到同步)状态。这个SYN同样不携带数据,且消耗一个序号。
第三次握手:客户端口收到服务端发过来的SYN和ACK确认报文后,还要向服务器给出确认,确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,客户端状态由原来的SYN_SENT状态变为ESTABLISHED(连接已确认建立)。TCP协议中ACK报文可以携带数据,携带数据的时候消耗序号
服务端收到报文后,由原来的SYN_RCVD变为ESTABLISHED,双方开始通信
三次握手有一个很形象的比喻
//客户端和服务端打电话
客户端:服务端你能听见吗
服务端:哎,能听见,你能听见我说话吗
客户端:能听见,有点事找你(如果没有这一步,服务端并不知道客户端是否能听见)
四次挥手关闭连接
先来解释一个TCP连接到底是谁关闭的,一般来说,谁断开都可以,对于我们常用的http来说,一般情况下是这样的
- 对于http1.0协议
- 如果响应头中有content-length头,则以content-length的长度就可以知道body的长度了,客户端在接收body时,就可以依照这个长度来接收数据,接收完后,就表示这个请求完成了,客户端请求断开。
- 如果没有content-length头,则客户端会一直接收数据,直到服务端主动断开连接,才表示body接收完了。
- 对于http1.1协议
- 如果响应头中的Transfer-encoding为chunked传输,则表示body是流式输出,body会被分成多个块,每块的开始会标识出当前块的长度,此时,body不需要通过长度来指定,客户端主动请求断开。
- 如果是非chunked传输,而且有content-length,则按照content-length来接收数据,接收完成后客户端请求断开连接。
- 如果是非chunked,并且没有content-length,则客户端接收数据,直到服务端主动断开连接。
下面说四次挥手的过程
我们假设客户端是请求关闭连接的一方,服务端为被动关闭
第一次挥手:客户端进程发请求关闭(或称释放)连接报文,并且停止发送数据。数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN_WAIT_1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
第二次挥手:服务器收到请求关闭报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE_WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE_WAIT状态持续的时间。
客户端收到服务器的确认请求后,此时,客户端就进入FIN_WAIT_2(终止等待2)状态,等待服务器发送关闭连接报文(在这之前还需要接受服务器发送的最后的数据)。
第三次挥手:服务器将最后的数据发送完毕后(或者没有需要发送的数据),就向客户端发送关闭连接报文,FIN=1,ack=u+1。由于服务器很可能又发送了一些数据,所以假定此时的序列号为seq=w,此时,服务器就进入了LAST_ACK(最后确认)状态,等待客户端的确认。
第四次挥手:客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME_WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2MSL(最大报文段寿命)的时间,当客户端撤销相应的TCB后,才进入CLOSED状态。
服务器只要收到了客户端发出的确认,立即进入CLOSED状态。撤销TCB后,就结束了这次的TCP连接。可以看到,服务器(被动断开方)结束TCP连接的时间要比客户端(主动断开方)早一些。
//客户端和服务端打电话
客户端:我的事说完了,有事情你说我听着,没什么事挂了哈
服务端:好的知道了,(好的,我还要和你说个事,你妈叫你回家吃饭)
服务端:行了挂了吧
客户端:哦,知道了,那我挂了
关于为什么要等待2MSL:
MSL(Maximum Segment Lifetime),这是TCP 对TCP Segment 生存时间的限制。
客户端发送最后一个ACK后,不能确保服务端一定能收到,假如ACK没有被服务端收到,超时后服务端重新进行第三次挥手,这时候如果A还在等待,又收到第三次挥手的FIN消息,证明ACK没有成功到达,这个时间至少是:服务端的超时时间 + FIN的传输时间,为了保证可靠,采用更加保守的等待时间2MSL。
如果客户端此时没有在等待状态直接CLOSED,服务端超时后发送FIN消息到客户端,客户端表示并不知道这数据包是干什么的,所以响应一个RST(用来异常的关闭连接,请自行了解),如果客户端有一个和服务端的新连接在这个端口上建立。这将可能导致后面建立的连接受到影响,TCP是可靠的连接,所以是不希望这种不靠谱的事情出现的。这种错误可以比喻为
//客户端和服务端打电话
客户端:我的事说完了,有事情你说我听着,没什么事挂了哈
服务端:好的知道了,(好的,我还要和你说个事,你妈叫你回家吃饭)
服务端:行了挂了吧
客户端:哦,知道了,那我挂了
(上面这一句因为信号不好等原因服务端没收到客户端就挂了电话)后面有事情又拨通了服务端电话(建立了新连接)
服务端:挂了吧
客户端:怎么回事,蛇精病啊,刚通了就让我挂
TCP报文首部标志位
关于报文首部格式,网上有很多,而且大部分都是以正确的,下面只介绍和本文有关的,
-
标志位:
- SYN(synchronous建立联机)
在连接建立时用来同步序号,当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意时,则应在响应的报文段中使SYN=1和ACK=1,因此,SYN置1就表示这是一个连接请求或连接接受报文。 - ACK(acknowledgement 确认)
仅当ACK=1时确认号字段才有效,TCP规定,连接建立后所有传送的报文段都必须把ACK置1. - PSH(push传送)
当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP就可以使用推送操作。 - FIN(finish结束)
用来释放一个连接,当FIN=1时,表示此报文段的发送方的数据已发送完毕,并要求释放运输连接。 - RST(reset重置)
当RST=1时,表明TCP连接中出现严重错误,必须释放连接,然后再重新建立运输连接。 - URG(urgent紧急)
当URG=1时,表明紧急字段有效,告诉系统此报文中有紧急数据,应尽快传送。
- SYN(synchronous建立联机)
- 其他:
- Sequence number(顺序号码)
seq是发送的数据包本身的序列号; - Acknowledge number(确认号码)
ack是对收到的数据包的确认,值是等待接收的数据包的序列号。即期望对方继续发送的那个数据包的序列号。
- Sequence number(顺序号码)