电子说
TCP连接的状态
首先介绍一下TCP连接建立与关闭过程中的状态。TCP连接过程是状态的转换,促使状态发生转换的因素包括用户调用、特定数据包以及超时等,具体状态如下所示:
CLOSED:初始状态,表示没有任何连接。
LISTEN:Server端的某个Socket正在监听来自远方的TCP端口的连接请求。
SYN_SENT:发送连接请求后等待确认信息。当客户端Socket进行Connect连接时,会首先发送SYN包,随即进入SYN_SENT状态,然后等待Server端发送三次握手中的第2个包。
SYN_RECEIVED:收到一个连接请求后回送确认信息和对等的连接请求,然后等待确认信息。通常是建立TCP连接的三次握手过程中的一个中间状态,表示Server端的Socket接收到来自Client的SYN包,并作出回应。
ESTABLISHED:表示连接已经建立,可以进行数据传输。
FIN_WAIT_1:主动关闭连接的一方等待对方返回ACK包。若Socket在ESTABLISHED状态下主动关闭连接并向对方发送FIN包(表示己方不再有数据需要发送),则进入FIN_WAIT_1状态,等待对方返回ACK包,此后还能读取数据,但不能发送数据。在正常情况下,无论对方处于何种状态,都应该马上返回ACK包,所以FIN_WAIT_1状态一般很难见到。
FIN_WAIT_2:主动关闭连接的一方收到对方返回的ACK包后,等待对方发送FIN包。处于FIN_WAIT_1状态下的Socket收到了对方返回的ACK包后,便进入FIN_WAIT_2状态。由于FIN_WAIT_2状态下的Socket需要等待对方发送的FIN包,所有常常可以看到。若在FIN_WAIT_1状态下收到对方发送的同时带有FIN和ACK的包时,则直接进入TIME_WAIT状态,无须经过FIN_WAIT_2状态。
TIME_WAIT:主动关闭连接的一方收到对方发送的FIN包后返回ACK包(表示对方也不再有数据需要发送,此后不能再读取或发送数据),然后等待足够长的时间(2MSL)以确保对方接收到ACK包(考虑到丢失ACK包的可能和迷路重复数据包的影响),最后回到CLOSED状态,释放网络资源。
CLOSE_WAIT:表示被动关闭连接的一方在等待关闭连接。当收到对方发送的FIN包后(表示对方不再有数据需要发送),相应的返回ACK包,然后进入CLOSE_WAIT状态。在该状态下,若己方还有数据未发送,则可以继续向对方进行发送,但不能再读取数据,直到数据发送完毕。
LAST_ACK:被动关闭连接的一方在CLOSE_WAIT状态下完成数据的发送后便可向对方发送FIN包(表示己方不再有数据需要发送),然后等待对方返回ACK包。收到ACK包后便回到CLOSED状态,释放网络资源。
CLOSING:比较罕见的例外状态。正常情况下,发送FIN包后应该先收到(或同时收到)对方的ACK包,再收到对方的FIN包,而CLOSING状态表示发送FIN包后并没有收到对方的ACK包,却已收到了对方的FIN包。有两种情况可能导致这种状态:其一,如果双方几乎在同时关闭连接,那么就可能出现双方同时发送FIN包的情况;其二,如果ACK包丢失而对方的FIN包很快发出,也会出现FIN先于ACK到达。
图解三次握手
实线代表服务器 的状态转换、虚线代表客户端 的转态转换;
文字讲解三次握手:
最开始客户端和服务器,都处于 CLOSED 状态,二者之间没有任何联系;
客户端向服务器发起连接请求,具体操作是发送一个 SYN 给服务器,客户端状态转而变为 SYN-SENT ,这是第一次握手 ;
由于客户端向服务器发送请求了,服务器被动的进入 LISTEN 状态;
如果服务器没有想要客户端的请求,则客户端得不到服务器的回应,请求就会超时,一旦请求超时,客户端的状态,就会再次进入 CLOSED ;
如果服务器响应了这个请求,具体操作是:接收到了客户端发送的 SYN,同时向客户端发送回应 SYN+ACK,表示我收到你的请求了;然后服务器的状态,进入 SYN-RECEIVED ; 这是第二次握手 ;
客户端收到服务器的回应 SYN+ACK,然后再次向服务器发送一个 ACK,表示我收到你的回应了 ;客户端的状态进入 ESTABLISHED ;这是第三次握手 ;
服务器收到客户端的响应 ACK 以后,状态进入 ESTABLISHED ;
至此,三次握手完成,客户端与服务器建立了可靠的连接,可以进行数据传输了 ;
上面有个特殊的地方:
假如客户端发送请求报文SYN的时候,服务器也发送了请求报文SYN ,则客户端进入 SYN-RECEIVED 状态,角色变为服务器; 一般这种情况是,没有绝对的客户端、服务器;通信的两台机器,可以在客户端和服务器之间进行角色的切换 ;
图解四次挥手
上面
实线代表主动关闭方 的状态转换、虚线代表被动关闭方 的转态转换;
文字讲解四次挥手:
主动关闭方,发送 FIN 给被动关闭方,然后进入 FIN-WAIT-1 状态 ;
被动关闭方,收到主动关闭方的 FIN,会送一个回应 ACK 给主动发送方,然后进入 CLOSE-WAIT ;
主动方收到被动方回应的ACK,并且也回应一个ACK,然后进入 FIN-WAIT-2 ;
既然主动方要关闭连接,那么被动方也不能死皮赖脸的不关啊,它也就向主动方发送一个请求FIN,然后进入 LAST-ACK ;
被动方收到主动方的回应ACK,进入 CLOSED
主动方收到被动方发送的 FIN 之后,发送回应 ACK(主动方在接收到被动方发送的FIN,将不再接受任何信息,但是可以发送信息),进入 TIME-WAIT
上面是有一方先于对方,发起关闭请求 ,下面说下,双方同时发起关闭请求的情况;
当双方同时发起关闭请求的时候,双方在发送完FIN 以后,都进入 FIN-WAIT-1 状态;然后如果双发又同时收到对方的FIN,以及同时收到对方回应的ACK,则直接进入 TIME-WAIT ;
如果双方同时收到FIN,但是没有同时收到ACK,则先进入 CLOSING,然后,在双方都收到 ACK 以后,再进入 TIME-WAIT ;
为什么在进入 TIME-WAIT 的状态以后,会等待 2MSL 的时间,再进入 CLOSED?
MSL 翻译为:报文最大生存时间 ;,2MSL则是两个报文最大生存世时间,等待这个时间的原因:是因为在网络不好的时候,有时候,需要重新发送报文,因此进入这里的等待下 ;
对Server与Client的影响
在详细了解TCP连接的状态和关闭方式后,我们会发现TIME_WAIT状态是一个坑爹的存在!主动关闭连接的一方在发送最后一个ACK包后,无论对方是否收到都会进入TIME_WAIT状态,等待2MSL的时间,然后才能释放网络资源。MSL就是Maximum Segment Lifetime(数据包的最大生命周期),是一个数据包能在互联网上生存的最长时间,若超过这个时间则该数据包将会消失在网络中。操作系统通常会将2MSL设为4分钟,最低不少于30秒,因而TIME_WAIT状态一般维持在30秒至4分钟。这个是TCP/IP协议必不可少的,是TCP/IP设计者设计的,也就是无法解决的。TIME_WAIT状态的存在主要有两个原因:
可靠地实现TCP全双工连接的终止。在关TCP闭连接时,最后的ACK包是由主动关闭方发出的,如果这个ACK包丢失,则被动关闭方将重发FIN包,因此主动方必须维护状态信息,以允许它重发这个ACK包。如果不维持这个状态信息,那么主动方将回到CLOSED状态,并对被动方重发的FIN包响应RST包,而被动关闭方将此包解释成一个错误(在Java中会抛出connection reset的SocketException)。因而,要实现TCP全双工连接的正常终止,必须能够处理四次握手协议中任意一个包丢失的情况,主动关闭方必须维持状态信息进入TIME_WAIT状态。
确保迷路重复数据包在网络中消失,防止上一次连接中的包迷路后重新出现,影响新连接。TCP数据包可能由于路由器异常而迷路,在迷路期间,数据包发送方可能因超时而重发这个包,迷路的数据包在路由器恢复后也会被送到目的地,这个迷路的数据包就称为Lost Duplicate。在关闭一个TCP连接后,如果马上使用相同的IP地址和端口建立新的TCP连接,那么有可能出现前一个连接的迷路重复数据包在前一个连接关闭后再次出现,影响新建立的连接。为了避免这一情况,TCP协议不允许使用处于TIME_WAIT状态的连接的IP和端口启动一个新连接,只有经过2MSL的时间,确保上一次连接中所有的迷路重复数据包都已消失在网络中,才能安全地建立新连接。
对于Client而言,每个连接都需要占用一个端口,而系统允许的可用端口数不足65000个(这也是在TCP参数优化后才能达到)。因此,如果Client发起过多的连接并主动关闭(假设没有重用端口或者连接多个Server),就会有大量的连接在关闭后处于TIME_WAIT状态,等待2MSL的时间后才能释放网络资源(包括端口),于是Client会由于缺少可用端口而无法新建连接。
对Server而言(特别是处理高并发短连接的Server),Server端与Client建立的连接是使用同一个端口的,即监听的端口,每个连接通过一个五元组区分,包括源IP地址、源端口、传输层协议号(协议类型)、目的IP地址、目的端口,因而在理论上,Server不受系统端口数的限制。但是,Server对每个端口上的连接数是有限制的,它要使用哈希表记录端口上的每个连接,并受到文件描述符的最大打开数的限制。所以,如果Server主动关闭连接,同样会有大量的连接在关闭后处于TIME_WAIT状态,等待2MSL的时间后才能释放网络资源(包括哈希表上的连接记录和文件描述符),于是Server会由于达到哈希表和文件描述符的限制而无法接受新连接,造成性能的急剧下滑,性能曲线会持续产生严重的波动。对于这种情况,有三种应对方式:
试图让Client主动关闭连接,由于每个Client的并发量都比较低,因而不会产生性能瓶颈。
优化Server的系统TCP参数,使其网络资源的最大值、消耗速度和恢复速度达到平衡。
改写TCP协议,重新实现底层代码,不过该方式难度很大,而且系统的稳定性和安全性可能受到影响。
Windows系统下的TCP参数优化
通常会采用修改注册表的方式改进Windows的系统参数。下面将为大家介绍Windows系统下的TCP参数优化方式,适用于Windows 2003、Windows XP、Windows 7以及Server版。对于具体的系统环境与性能需求,优化方式会有所差异,效果也不尽相同,仅是个人的建议。所有的优化操作都通过修改注册表实现,需要使用regedit命令进入注册表并创建或修改参数,修改完成后需要重启系统,以使之生效。以下使用的参数值均为10进制。
1. TCPWindowSize
TCPWindowSize的值表示TCP的窗口大小。TCP Receive Window(TCP数据接收缓冲)定义了发送端在没有获得接收端的确认信息的状态下可以发送的最大字节数。此数值越大,返回的确认信息就越少,相应的在发送端和接收端之间的通信就越好。此数值较小时可以降低发送端在等待接收端返回确认信息时发生超时的可能性,但这将增加网络流量,降低有效吞吐率。TCP在发送端和接收端之间动态调整一个最大段长度MSS(Maximum Segment Size)的整数倍。MSS在连接开始建立时确定,由于TCP Receive Window被调整为MSS的整数倍,在数据传输中完全长度的TCP数据段的比例增加,故而提高了网络吞吐率。
缺省情况下,TCP将试图根据MSS来优化窗口大小,起始值为16KB,最大值为64KB。TCPWindowSize的最大值通常为65535字节(64KB),以太网最大段长度为1460字节,低于64KB的1460的最大整数倍为62420字节。
重置(RST)
几种TCP连接中出现RST的情况
端口未打开
请求超时
提前关闭
在一个已关闭的socket上收到数据
TCP重置是一个好的事情,如果没有reset,我们将会遇到各种各样的TCP网络连接问题。需要注意的是导致reset的原因是多种多样的,不仅仅是两端的节点还有可能是应用程序,追查问题时最重要的是查看包的状态以及其重传。
一个案例
当我们仅仅打开服务端之后(端口号为5188),我们来看看所处的状态。
打开服务端:
调用命令查看所有的网络状态:netstat
然后,我们通过命令:摘取有关tcp的状态:netstat -an |grep tcp
紧接着为了删减出有效的信息,我们只需要tcp协议,5188这个端口,我们可以这样做:
netstat -an|grep tcp|grep 5188
此刻,可以看到,这里的状态是处于LISTEN,调用的accept函数还是在阻塞着,等待着返回。
这时,再次打开客户端,继续观察一下状态:
然后,继续调用之前的命令:
netstat -an|grep tcp|grep 5188
当客户端一打开,那么就完成了TCP的建立,这里,我们可以看到有两个是:ESTABLISHED
其中第二行的42555表示的是客户端所打开的端口,5188是服务端所打开的端口,客户端连向了服务器端。
由于我们上面的测试是在同一台主机上的,所以会出现上面的三种信息
而对于其他的状态而言,只是因为状态的转化时间非常短(三次握手,四次挥手完成的特别快),我们不
去探究具体的状态,下面
1.查找服务器进程:
ps -ef | grep echoserv
分析其pid号,知道了我们此刻打开的是中间的这个服务端(21858,21849)
所以,此刻,我们杀死这个进程:
kill -9 21858
到啦这里,我们再次查看一下状态:
至于为什么会产生一个FIN_WAIT2, 而不是TIME_WAIT状态呢?这是因为:我们程序中是这样处理的,服务端关闭之后,然后客户端接收到啦这个分节,并向服务端发送了当前的分节确认,然后自己阻塞在了从键盘获取字符的这个位置,并不能运行到函数read处去,也就是说,read函数压根就不会返回0,所以客户端就不会重新向服务端重新发送关闭连接的分节,也就停留在此刻了,同样的,
服务端接受到啦确认分节,那么自己的状态就变成了FIN_WAIT_2,这样就解释的说明问题了
全部0条评论
快来发表一下你的评论吧 !