前面两篇文章介绍了TCP状态变迁,以及通过实验演示了客户端和服务端的正常状态变迁。
下面就来看看TCP状态变迁过程中的几个特殊状态。
在TCP连接建立的过程中,当服务端接收到[SYN]包后,就会发送[SYN, ACK]包,然后进入SYN_RCVD状态。
根据前面文章的介绍,服务器的上述行为被称为被动打开,并且会等待来自客户的的[ACK]包来完成TCP连接的建立。但是,如果此时客户端没有响应,服务端就会超时重传[SYN, ACK]包。
回想一下我们在"动手学习TCP: 环境搭建"一文中使用的例子,这个例子就只是客户端向服务端发送一个TCP连接建立请求包,然后就进入等待状态了。
让我们再次运行这个例子,通过Wireshark抓包可以看到,虚拟机中的服务端进行了五次超时重传,间隔为3s,6s,12s,24s,一共45s;但是,当第五个[SYN, ACK]包发送后,服务器将会继续等待48s,最终第五次重传也超时了。
在服务器重传这段时间,通过虚拟机中的命令行运行 netstat -anp TCP | findstr "192.168.56" 命令,会看到服务器处于SYN_RCVD状态。
从上面的实验结果可以看到,当服务端收到客户端的TCP连接请求后,会发送[SYN, ACK]包,进入SYN_RCVD状态。如果没有收到客户端的确认,服务器会尝试重传,并保持SYN_RCVD状态一段时间(通常是30秒到2分钟)。
由于服务端的SYN_RCVD状态,就有了SYN Flood攻击。
所谓的SYN Flood攻击就是,恶意的客户端给服务端发了一个SYN后,就下线了,于是服务器需要默认等93s(通常是30秒到2分钟,上面的例子是93s)才会断开连接。
这样,攻击者就可以把服务器的SYN连接的队列耗尽,让正常的连接请求不能处理。
对于如何避免SYN Flood攻击,服务端有很多设置方式,这里就不介绍了,有兴趣可以网上查查。
在客户端的正常状态变迁中,客户端主动终止TCP连接,然后就会从TIME_WAIT状态到CLOSED状态。
TIME_WAIT状态也称为2MSL(Maximum Segment Lifetime)等待状态,这个设置是TCP中4中定时器之一(另外的3个定时器后面介绍)。
RFC793定义了MSL为2分钟,但是在实现中,MSL一般为30秒,1分钟或者两分钟。
之所以有一个TIME_WAIT状态,而不是直接转换成CLOSED状态,主要有下面两个原因:
当一端进入TIME_WAIT状态后,所产生的效果就是该端口在2MSL这段时间中不能被再次使用。
看一个实验例子,由于 操作系统不能检测到Pcap.Net实现的客户端的TCP连接状态,所以通过Python实现了一个简单的socket客户端,并强制指定客户端的端口号为3333:
from socket import * import time Client_ADDR = ("192.168.56.101", 3333) Server_ADDR = ("192.168.56.102", 8081) BUFSIZ = 1024 client = socket(AF_INET, SOCK_STREAM) client.bind(Client_ADDR) client.connect(Server_ADDR) print "client connect to server" print "quit after 5 seconds" time.sleep(5) client.close()
当程序运行后,可以通过netstat命令看到客户端显示进入"ESTABLISHED"状态,当终止连接后,就进入了"TIME_WAIT"状态。
这时,当再次运行客户端程序的时候,就会遇到下面的异常,提示端口被占用:
从上面的介绍可以看到,主动终止TCP连接的一端会进入TIME_WAIT状态,该端TCP连接的端口将在2MSL时间中不可用。
如果在大并发的短连接情况下,TIME_WAIT 就会很多,系统的可用端口资源就会面临耗尽的情况。
这也就说明了HTTP的KeepAlive对HTTP服务器是多么的重要,在设置KeepAlive的情况下,浏览器会重用一个TCP连接来处理多个HTTP请求,减缓TIME_WAIT带来的影响。
关于复位报文段,它不是一个TCP状态,但是确实TCP状态变迁中不可少的一部分,所以在这里进行简单的介绍。
所谓复位报文段就是TCP首部中,设置RST标志的TCP包。一般来说,无论何时一个报文段发送过程中遇到连接错误,TCP都会发出一个[RST]包来重置该TCP连接。
一般下面情况下会经常碰到[RST]包:
这次依然运行"动手学习TCP: 环境搭建"中的例子,只是把目标端口改为"1234"。
EndPointInfo endPointInfo = new EndPointInfo(); endPointInfo.SourceMac = "08:00:27:00:C0:D5"; endPointInfo.DestinationMac = "08:00:27:70:A6:AE"; endPointInfo.SourceIp = "192.168.56.101"; endPointInfo.DestinationIp = "192.168.56.102"; endPointInfo.SourcePort = 3330; //endPointInfo.DestinationPort = 8081; endPointInfo.DestinationPort = 1234;
运行程序,由于虚拟机中的"1234"端口并不是一个TCP监听端口,所以就会收到来自虚拟机的[RST]包:
前面已经看到,正常终止一个TCP连接需要进行四次挥手,这也被称为有序释放(orderly release)。
但是,也有情况是通过[RST]包来释放一个连接,这种情况被称为异常释放(abortive release)。
异常终止一个连接对应用程序来说有两个优点:
本文介绍了TCP状态转换中的两个特殊状态:SYN_RCVD和TIME_WAIT。
SYN_RCVD状态会使服务端的特定端口,在一段时间内重传[SYN, ACK]包,直到超时或者客户端有相应;在该端时间内,服务器的该端口被占用。TIME_WAIT状态则是,主动关闭TCP连接的一端,会保持2MSL的时间后,才进入CLOSED状态。
后半部分简单介绍了复位报文段,以及复位报文段经常使用的情况。