TCP的连接建立(TCP3次握手)
TCP3次握手 three way handshaks 其实是在一次握手中交换了三个报文,更准确的翻译是“三报文握手”(RFC973 three way/three message handshaks) TCP建立连接的过程叫做握手,握手需要在客户端和服务器之间交换三个TCP报文段
- 最初两端的TCP进程都处于CLOSED(关闭)状态。
在本例子中,A主动打开连接,B被动打开连接。
- 服务器B启动,B的TCP服务器进程会先创建传输控制块 TCB,准备接受客户端的连接请求,然后服务器进程就处于
LISTEN 收听
状态,等待客户的连接请求,如有则做出响应。 - (第一次)客户端A的TCP进程,在准备建立TCP连接之前,先会创建传输控制模块 TCB。建立连接,向B发出连接请求报文段。
- 此时报文首部的同步位
SYN=1
, 同时选择一个初始序号seq=x
。 - 报文:
SYN=1 seq=x
- A的TCP进程进入
SYN-SENT 同步已发送
状态
- 此时报文首部的同步位
传输控制模块 TCB(Transmission Control Block)存储了每一个连接中的重要信息,如:TCP连接表,指向发送和接受缓存的指针,指向重传队列的指针,当前的发送和接收序号等等
- (第二次)服务器B收到连接请求报文后,若同意建立连接,则向A发出确认。
- 在报文中把
SYN
位和ACK
位都置1,确认号是ack=x+1
,同时也选择一个初始序号seq = y
。 - 报文:
SYN=1 ACK=1 seq=y ack=x+1
- 此时服务器B的TCP进程进入
SYN-RCVD 同步收到
状态
- 在报文中把
TCP标准规定,SYN报文段(即SYN=1的报文段)不能携带数据,但要消耗一个掉序号seq
- (第三次)客户端A收到B的确认(第二次握手)后,还要向服务器B给出确认。
- 确认报文段的ACK置1,确认号
ack = y + 1
,而自己的序号seq = x + 1
。 - 报文:
ACK=1 seq=x+1 ack=y+1
- 此时,TCP的连接已经建立,A进入
ESTABLISHED
状态
- 确认报文段的ACK置1,确认号
TCP标准规定,ACK报文段可以携带数据,但如果不携带数据则不消耗序号。不携带数据的话,下一个报文段的序号仍是
seq=x+1
- B收到A的确认后也进入
ESTABLISHED
状态
第二次握手的报文可以拆成两个报文段,先发送一个确认报文段
ACK=1 ack=x+1
, 再发送一个同步报文段SYN=1 seq=y
, 这样的过程就变成了四次握手,但效果是一样的
为什么A最后还要发送一次确认呢?(为什么是三次握手呢?) 为了防止A发出的已失效的连接请求报文突然又传送到了B而产生错误:
- 正常情况:
- A发出连接请求,但是因请求报文丢失而未收到确认,于是A再重传一次连接请求。
- 重传的请求收到了确认,建立起了连接
- 数据传输完成,释放了连接。A一共发了两个连接请求,第一个丢失,第二个到达B,正常,未产生“已失效的连接请求报文”。
- 异常情况:
- A发出的第一个连接请求并没有丢失,而是在因为某些原因滞留了,而延迟到达。
- 这本是一个失效的连接请求,但B收到此请求后会误以为是A发出的一个新的连接请求,于是向A发出确认报文,同意建立连接。
- 若此时不采用第三次确认再建立连接的话,只要B发出确认,新的连接就建立了。但由于这并不是A的有效连接请求,所以A不会理睬B的确认,也不会向B发送数据,B却以为新的运输连接已经建立了,并一直等待A发来的数据,B的许多资源就白白浪费了。
- 三次握手可以防止上述问题,在上述情况下,A不会向B的确认发出确认。B没有收到确认就知道A没有要求建立连接。
TCP的连接释放(TCP4次挥手)
TCP连接释放的过程更复杂
- 数据传输结束后,通信双方AB都可以释放连接,释放之前都处于
ESTASLISHED
状态,以A释放连接为例: - (第一次)A的应用进程向TCP发出连接释放报文段,并停止再发送数据,主动关闭TCP连接。
- A把连接释放报文段首部的终止控制位FIN置1,序号seq=u,u等于A前面已传送过的数据的最后一个字节的序号加1。
- 报文:
FIN=1 seq=u
- 此时A进入
FIN-WAIT-1
状态
TCP标准规定,FIN报文段(即SYN=1的报文段)即使不携带数据,也要消耗一个掉序号seq
-
(第二次)B收到连接释放报文后即发出确认,
- 确认号是
ack=u+1
,报文段自己的序号取seq=v
,v等于B前面已传送过的数据的最后一个字节的序号加1 - 报文:
ACK=1 seq=v ack=u+1
- 此时B进入
CLOSE-WAIT
状态,TCP进程通知应用进程,A到B方向的连接就释放掉了。 - A收到B发出的确认后(第二次挥手),就进入
FIN-WAIT-2
状态,等待B发出的连接释放报文段
- 确认号是
-
此时的TCP连接处于半关闭 half-close状态,即A已经没有数据要发送了,但B若发送数据,A仍要接收。
-
(第三次)若B已经没有要向A发送的数据,其应用进程就通知TCP释放连接。
- 此时B的连接释放报文须使
FIN=1
,假设半关闭状态又发送了一些数据报,现在的序号为seq=w
,且须重复上次的确认号ack=u+1
- 报文:
FIN=1 ACK=1 seq=w ack=u+1
- 此时B进入
LAST-ACK 最后最后确认状态
,等待A的确认
- 此时B的连接释放报文须使
-
(第四次)A在收到B的连接释放报文段后,必须对此发出确认
- 在确认报文段中把ACK置1,确认号
ack=w+1
,而自己的序号是seq=u+1
- 报文:
ACK=1 seq=u+1 ack=w+1
- A进入
TIME-WAIT 时间等待
状态。注意,此时TCP连接还没有释放掉。必须经过时间等待计时器 TIME-WAIT-TIMER设置的时间2MSL后,A才进入到CLOSE
状态 - 而B收到A的确认就进入了
CLOSE
状态
- 在确认报文段中把ACK置1,确认号
时间MSL叫做最长报文段寿命 Maximun Segment Life, RFC 793建议设置为2分钟。TCP允许不同的实现可根据不同的具体情况使用更小的MSL值
- A进入
CLOSE
状态,撤销相应的传输控制块TCB后,就结束了这次的TCP
为什么A在 TIME-WAIT
状态必须等待2MSL的时间?
- 1 为了保证A发送的最后一个ACK报文段能够到达B
- 这个确认报文段(第四次挥手)可能丢失,而
LAST-ACK
状态的B若收不到这个确认就会超时重传(第三次挥手)FIN+ACK
,接着A重传(第四次挥手)一次确认,重置2MSL计时器。最后A和B都正常CLOSE
。 - 若A在(第四次挥手)确认报文后不等待就进入
CLOSE
,但确认报文丢失时,就收不到B的重传(第三次挥手)FIN+ACK
,这样B就无法正常进入CLOSE
状态。
- 这个确认报文段(第四次挥手)可能丢失,而
- 2 防止“已失效的连接请求报文”出现在本连接中
- A在发送完最后一个确认(第四次挥手)后,再经过2MSL,就可以使本次TCP连接中所产生的所有报文段都从网络中消失。下一次的新TCP就不会出现“已失效的连接请求报文”
除了时间等待计时器外,TCP还有个保活计时器 keepalive timer。设想这种情况: 客户端与服务器建立了TCP连接,但后来客户端突然出现故障,显然服务器以后就不能再收到客户端的数据,因此需要有个机制使得服务器不要再白白等下去浪费资源,这个机制就是保活计时器。服务器每收到一次客户端数据就重置保活计时器,时间的设置通常是两小时。若两小时没有收到客户数据,服务器就发送一个探测报文,每隔75s发送一次,连续10个仍无响应就认为客户端出现故障,关闭这个TCP连接。
参考
- 《计算机网络》谢希仁