TCP状态机
TCP连接状态
一个TCP在其生命周期会历经多种状态。
状态的转换原因一是因为应用程序的主动,一是接收到网络请求的被动。
上图包含11种状态,分别对其进行描述
状态 | 描述 |
---|---|
CLOSED | 关闭状态——初始状态 |
SYN_SENT | 发送连接请求后等待来自远程端点的确认。TCP第一次握手后客户端所处的状态 |
ESTABLISHED | 连接已建立。连接数据传输阶段的正常状态 |
FIN_WAIT_1 | 已发送FIN报文,正等待来自远程TCP的终止连接请求或终止请求的确认 |
FIN_WAIT_2 | FIN_WAIT1状态的TCP节点现在已经收到了对端TCP节点发来的ACK(半关闭状态) |
TIME_WAIT | 完成主动关闭后,TCP节点接收到了FIN报文。这表示对端执行了一个被动关闭。2MSL超时时间 |
LISTEN | TCP正等待从对端TCP节点发来的连接请求 |
SYN_REVD | 该端点已经接收到连接请求并发送确认。 |
CLOSE_WAIT | 该端点已经收到来自远程端点的关闭请求,此TCP正在等待本地应用程序的连接终止请求。 |
LAST_ACK | 应用程序执行被动关闭,而之前处于CLOSE_WAIT状态的节点发送一个FIN报文给对端,并等待对端的确认。当收到对端发来的确认ACK报文时,连接关闭,相关的内核资源都会得到释放。 |
CLOSING | 之前处在FIN_WAIT1状态的TCP节点正在等待对端发送ACK,但却收到了FIN。这表示对端也正在尝试执行一个主动关闭。(换句话说,这两个TCP节点几乎在同一时刻发送了FIN报文。即同时关闭) |
三次握手
- 第一次握手:建立连接。
- 客户端发送连接请求报文段,SYN置为1,Sequence Number为J;然后,客户端进入SYN_SEND状态,等待服务器的确认。
- 第二次握手:服务器收到SYN报文段。
- 服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为J+1(Sequence Number+1);
- 同时,服务器还要发送SYN请求信息,将SYN位置为1,SequenceNumber为K;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态。
- 第三次握手:客户端收到服务器的SYN+ACK报文段。
- 客户端将Acknowledgment Number设置为K+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
四次挥手
第一次挥手:A端(客户端或服务器),设置Sequence Number和Acknowledgment Number,向B端发送一个FIN报文段。此时,A端进入FIN_WAIT_1状态,这表示A端没有数据要发送给主机2了。
第二次挥手:B端收到了A端发送的FIN报文段,向A端回一个ACK报文段,Acknowledgment Number为Sequence Number加1。A端收到ACK,进入FIN_WAIT_2状态。
第三次挥手:B端向A端发送FIN报文段,请求关闭连接,同时B端进入CLOSE_WAIT状态。
第四次挥手:A端收到B端发送的FIN报文段,向B端发送ACK报文段,然后A端进入TIME_WAIT状态。
B端收到A端的ACK报文段以后,就关闭连接。此时,A端等待2MSL后依然没有收到回复,则证明B端已正常关闭,那好,A端也可以关闭连接了。
RST报文段
无论何时一个报文段发往基准的连接出现错误,TCP都会发出一个复位报文段。
基准的连接:指由目的IP地址和端口号以及源IP地址和端口号指明的连接。
RST报文段,接收方不会进行确认。收到RST的一方将终止该连接,并通知应用层连接复位。
应用场合:
到不存在的端口的连接请求:服务器程序端口未打开而客户端来连接。比如主机1向主机2发送一个SYN请求,表示想要连接主机2的40000端口,但是主机2上根本没有打开40000这个端口,于是就向主机1发送了一个RST。
异常终止一个连接:终止一个连接的正常方式是一方发送FIN。有事这也称为有序释放,因为所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失。
但是也有可能发送一个复位报文段而不是FIN来中途释放一个连接,有时也被称为异常释放。
异常释放有两个优点:
- 丢弃任何待发数据并立即发送复位报文段(优先级高)
- RST的接收方会区分另一端执行的是异常关闭还是正常关闭
检测半打开连接:如果一方已经关闭或者异常终止连接,而另一方却不知道,即半打开状态。
比如,客户端因为一些原因重启,而服务器不知道TCP已经处于半打开状态,客户端再次连接,服务器将重新创建一个新的服务器程序。这样会导致服务器主机上有很多半打开的TCP连接。所以需要在重新连接的时候先发送一个RST报文段。断开之前的半打开状态的TCP连接。
向一个已经关闭的连接发送报文段。
close(sockfd)时,直接丢弃接收缓冲区未读取的数据,并给对方发一个RST。这个是由SO_LINGER选项来控制的。
几个疑问
为什么需要三次握手
客户端还要再发送一次确认是为了防止已失效的连接请求报文段突然又传到了服务器,因而产生错误。
正常情况下:A发出连接请求,但因为丢失了,故而不能收到B的确认。于是A重新发出请求,然后收到确认,建立连接,数据传输完毕后,释放连接,客户端发了2个请求,一个丢掉,一个到达,没有“已失效的报文段”。
但是,某种情况下,客户端的第一个在某个节点滞留了,延误到达。本来这是一个早已失效的报文段,然而在客户端发送第二个,并且得到服务器的回应,建立了连接以后,这个报文段竟然到达了,于是服务器就认为,客户端又发送了一个新的请求,于是发送确认报文段,同意建立连接,假若没有三次的握手,那么这个连接就建立起来了(有一个请求和一个回应),此时,客户端收到服务器的确认,但服务器知道自己并没有发送建立连接的请求,因为不会理睬服务器的这个确认,于是呢,客户端也不会发送任何数据,而服务器却以为新的连接建立了起来,一直等待客户端发送数据给自己,此时服务器的资源就被白白浪费了。但是采用三次握手的话,客户端就不发送确认,那么服务器由于收不到确认,也就知道并没有要求建立连接。
因此,第三次握手,客户端发送一次确认是为了防止服务器连接开销。
为什么需要四次挥手
TCP建立连接要进行三次握手,而断开连接要进行四次。这是由于TCP的半关闭造成的。
因为TCP连接是全双工的(即数据可在两个方向上同时传递),所以进行关闭时每个方向上都要单独进行关闭。这个单方向的关闭就叫半关闭。当一方完成它的数据发送任务,就发送一个FIN来向另一方通告将要终止这个方向的连接。
注意:
- 发送了FIN只是表示这端不能继续发送数据(应用层不能再调用send发送),但是还可以接收数据。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。
- 在很多时候,TCP连接的断开都会由TCP层自动进行,例如你CTRL+C终止你的程序,TCP连接依然会正常关闭。
为什么要等待2MSL
MSL即Maximum Segment Lifetime,也就是最大报文生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。引用《TCP/IP详解》中的话:“它(MSL)是任何报文段被丢弃前在网络内的最长时间”。RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等。
TCP的TIME_WAIT状态需要等待2MSL,当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间,等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。
概括原因如下:
- 为了保证A发送的最后一个ACK报文段能够到达B。即最后这个确认报文段很有可能丢失,那么B会超时重传,然后A再一次确认,同时启动2MSL计时器。如果没有等待时间,发送完确认报文段就立即释放连接的话,B就无法重传了(连接已被释放,任何数据都不能出传了),因而也就收不到确认,就无法按照步骤进入CLOSE状态,即必须收到确认才能close。
- 防止“已失效的连接请求报文段”出现在连接中。经过2MSL,那些在这个连接持续的时间内,产生的所有报文段就可以都从网络中消失。即在这个连接释放的过程中会有一些无效的报文段滞留在楼阁结点,但是呢,经过2MSL这些无效报文段就肯定可以发送到目的地,不会滞留在网络中。这样的话,在下一个连接中就不会出现上一个连接遗留下来的请求报文段了。
可以看出:B结束TCP连接的时间比A早一点,因为B收到确认就断开连接了,而A还得等待2MSL。