简介
TCP提供面向有连接的通信传输,面向有连接是指在数据通信开始之前先做好通信两端之间的准备工作。
TCP在数据通信之前,通过TCP首部发送一个SYN包作为建立连接的请求等待确认应答(TCP中发送第一个SYN包的一方叫做客户端,接收这个的一方叫做服务端)。如果对端发来确认应答,则认为可以进行数据通信。如果对端的确认应答未能到达,就不会进行数据通信。此外,在通信结束时会进行断开连接的处理(FIN包)。
一个连接的建立与断开,正常过程至少需要来回发送7个包才能完成(建立一个TCP连接需要发送3个包,这个过程也称作“三次握手”,断开一个TCP连接需要发送4个包,这个过程也称作“四次挥手”)。
TCP报文段首部
在具体理解三次握手与四次挥手的细节时,需要先了解一下TCP报文段的首部格式:
主要需要了解的是:
- 序号 seq 占4个字节,范围[0,223-1],共223(42 9496 7296)个序号。TCP是面向字节流的,在一个TCP连接中传送的字节流中的每一个字节都按顺序编号。
- 确认号 ack 占4个字节,是期望收到对方下一个报文段的第一个数据字节的序号。
- 确认 ACK 仅当ACK=1时确认号字段才有效,当ACK=0时,确认号无效。TCP规定,在建立连接后所传送的报文都必须把ACK置1。
- 同步 SYN 在连接时用来同步序号,当SYN=1而ACK=0时,表明这是一个连接请求报文段,对方若同意连接,在应在响应的报文段中使用SYN=1和ACK=1。因此,SYN置为1就表示这是一个连接请求或连接接受报文。
- 终止 FIN 用来释放一个连接,当FIN=1时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接。
三次握手
- 最初两端的TCP进程都处于CLOSE(关闭)状态。上图中A主动打开连接,B被动打开连接。
- B打开连接后处于LISTEN(监听状态),等待客户的连接请求。
- A向B发送请求报文,SYN=1,ACK=0,选择一个初始序号seq=x。
- B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为ack= x 1,同时也选择一个初始的序号 seq=y。
- A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为ack= y 1,序号为 seq=x 1。
- B 收到 A 的确认后,连接建立。
必须握手三次而不是两次的原因
主要是防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
建立连接后,客户端出现故障时的机制
TCP设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若2小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
四次挥手
- A 发送连接释放报文,FIN=1。
- B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。
- 当 B 不再需要连接时,发送连接释放报文,FIN=1。
- A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接。
- B 收到 A 的确认后释放连接。
四次挥手的细节
CLOSE-WAIT
客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。
TIME-WAIT
客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。原因有二:
- 为保证A发送的最后一个ACK报文段能够到达B A发送的这个ACK报文段有可能丢失,如果 B 没收到 A 发送来的确认报文,那么A就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。
- 防止“已经失效的连接请求报文段”出现在本链接中 A在发送完最后一个ACK报文段后,再经过时间2MSL,就可以使本连接的时间内所产生的所有报文段都从网络中消失。这样下一个新的连接中就不会出现这种旧的连接请求报文段。
为何建立连接协议是三次握手,而关闭连接却是四次挥手?
因为服务端B的LISTEN状态下的SOCKET当收到SYN报文的连接请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当B收到对方A的FIN报文通知时,它仅表示客户端A没有数据发送给B了;但未必B的数据都全部发送给A了,所以B可能未必会马上会关闭SOCKET,而是先发送一个ACK应答信号,继续将剩余的数据发送给A之后,再发送FIN报文给A表示B同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
参考:
https://zhuanlan.zhihu.com/p/58603455
https://blog.csdn.net/qzcsu/article/details/72861891
《图解TCP/IP(第5版)》人民邮电出版社