TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。这种两端间连接的建立与无连接协议如UDP不同。一端使用UDP向另一端发送数据报时,无需任何预先的握手。
两个进程在使用TCP交换数据之前,它们之间必须建立一条连接。完成后,要关闭这个连接。
一个TCP连接由一个4元组唯一确定:本地IP地址、本地端口号、远端IP地址和远端端口号。
TCP 连接的建立
- 客户端发送请求连接(SYN=1,ACK=0),TCP规定SYN=1时不能携带数据,但要消耗掉一个序号(seq=x)。
- 服务端回复确认(SYN=1,ACK=1),同样要消耗掉一个序号(seq=y),确认号ack=x+1。
- 客户端再进行一次确认(ACK=1),序号seq=x+1,确认号ack=y+1。TCP的标准规定,ACK报文段可以携带数据,但如果不携带数据则不消耗序号,因此,如果不携带数据,则下一个报文段的序号仍为seq=x+1。这时,TCP连接已经建立,客户端进入ESTABLISHED(已建立连接)状态。
这个过程即为TCP三次握手。
TCP 连接的终止
数据传输结束后,通信的双方都可以释放连接,并停止发送数据。
- A主动关闭TCP连接,发送连接释放报文段中FIN=1,序号为seq=u,该序号等于前面已经传送数据的最后一个数据的序号加1。此时A进入FIN-WAIT-1(终止等待1状态)状态,等待B的确认,TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
- B收到连接释放报文段后立即发出确认(ACK=1),序号seq=v,确认号ack=u+1。然后B进入CLOSE-WAIT(关闭等待状态)状态,此时TCP服务器进程应该通知上层应用进程,A到B这个方向的连接释放了,这是TCP处于半关闭状态,即A没有数据要发了,但B要发数据,A仍需要接收,也就是说从B到A的连接并没有关闭,这个状态可能持续一段时间。
- A收到B的确认后,就进入FIN-WAIT-2(终止等待2状态),等待B发出连接释放报文段,如果B没有要向A发送的数据,其应用进程就通知TCP释放连接。B发出连接释放段,FIN=1,序号seq=w,确认号ack=u+1。此时B进入LAST-ACK(最后确认)状态,等待A的确认。
- A收到B的连接释放请求后,发出确认报文。ACK=1,确认号ack=w+1,序号seq=u+1,然后进入TIME-WAIT(时间等待)状态。这是TCP连接还没有释放掉,必须经过时间等待定时器设置的时间2MSL后,A才进入CLOSED状态,MSL叫做最长报文寿命,而B只要收到A的确认,就进入了CLOSED状态。二者都进入CLOSED状态后,连接就完全释放了。
同时打开
正常情况下,传输连接都是由一方主动发起的,但也有可能双方同时主动发起连接,此时就会发生连接碰撞,最终只有一个连接能够建立起来。
当出现同时发出连接请求时,则两端几乎在同时发送一个SYN字段置1的数据段,并进入SYN_SENT状态。当每一端收到SYN数据段时,状态变为SYN_RCVD,同时它们都再发送SYN字段置1,ACK字段置1的数据段,对收到的SYN数据段进行确认。当双方都收到对方的SYN+ACK数据段后,便都进入ESTABLISHED状态。最终建立的是一个TCP连接,而不是两个,这点要特别注意。
同时关闭
与同时打开一样,TCP传输连接关闭也可以由双方同时主动进行(正常情况下都是由一方发送第一个FIN数据段进行主动连接关闭,另一方被动接受连接关闭)。
当两端对应的网络应用层进程同时调用CLOSE原语,发送FIN数据段执行关闭命令时,两端均从ESTABLISHED状态转变为FIN_WAIT_1状态。任意一方收到对端发来的FIN数据段后,其状态均由FIN_WAIT_1转变到CLOSING,并发送最后的ACK,当收到最后的ACK时,状态变化为TIME_WAIT。在等待2MSL后进入到CLOSED状态,最终释放整个TCP传输连接。
2MSL 等待状态
TIME_WAIT状态也称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。我们知道这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
RFC 793 [Postel 1981c]指出MSL为2分钟。然而,实现中的常用值是30秒,1分钟,或2分钟。
为什么A需要在TIME_WAIT状态等待2MSL时间?
- 为了保证A发送的最后一个ACK报文段能够到达B。该ACK报文段很有可能丢失,因而使处于在LIST—ACK状态的B收不到对已发送的FIN+ACK报文段的确认,B可能会重传这个FIN+ACK报文段,而A就在这2MSL时间内收到这个重传的FIN+ACK报文段,接着A重传一次确认,重新启动2MSL计时器,最后A和B都进入CLOSED状态。如果A在TIME—WAIT状态不等待一段时间就直接释放连接,到CLOSED状态,那么久无法收到B重传的FIN+ACK报文段,也就不会再发送一次确认ACK报文段,B就无法正常进入CLOSED状态。
- 防止已失效的请求连接出现在本连接中。在连接处于2MSL等待时,任何迟到的报文段将被丢弃,因为处于2MSL等待的、由该插口(插口是IP和端口对的意思,socket)定义的连接在这段时间内将不能被再用,这样就可以使下一个新的连接中不会出现这种旧的连接之前延迟的报文段。
客户执行主动关闭并进入TIME_WAIT是正常的。服务器通常执行被动关闭,不会进入TIME_WAIT状态。这暗示如果我们终止一个客户程序,并立即重新启动这个客户程序,则这个新客户程序将不能重用相同的本地端口。这不会带来什么问题,因为客户使用本地端口,而并不关心这个端口号是什么。
然而,对于服务器,情况就有所不同,因为服务器使用熟知端口。如果我们终止一个已经建立连接的服务器程序,并试图立即重新启动这个服务器程序,服务器程序将不能把它的这个熟知端口赋值给它的端点,因为那个端口是处于2MSL连接的一部分。在重新启动服务器程序前,它需要在1~4分钟。