TCP 3次握手和4次挥手

2020-07-08 · 12 min read

TCP的连接建立(TCP3次握手)

TCP3次握手 three way handshaks 其实是在一次握手中交换了三个报文,更准确的翻译是“三报文握手”(RFC973 three way/three message handshaks) TCP建立连接的过程叫做握手,握手需要在客户端和服务器之间交换三个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 状态

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连接释放的过程更复杂

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的确认
  • (第四次)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 状态

时间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连接。

参考

  • 《计算机网络》谢希仁

Profile picture

Blogs by Leo Yang who lives and works in Chengdu writing interesting things. You should follow me on Github 😁