TCP 的 三次握手 四次握手

2,800 阅读7分钟

解释 

  • 同步 SYN :synchronous。建立连接,将 SYN = 1。
  • 序号 seq: sequence。第一个字节的编号随机产生。
  • 确认位 ACK : acknowledgement 。
  • ack : 表示确认字段的值。(对哪个进行确认)。
  • 结束 FIN : finish。FIN = 1 表示希望断开连接。

状态 

  • SYN-SENT : 同步已发送。
  • SYN-RCVD:同步收到。
  • ESTABLISHED: 已建立连接。
  • FIN-WAIT-1:终止等待1。
  • FIN-WAIT-2:终止等待2。
  • CLOSE-WAIT: 关闭等待。 
  • LAST-ACK : 最后确认。
  • TIME-WAIT: 时间等待。 
  • CLOSED :关闭状态。

三报文握手

TCP建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个 TCP 报文段。



最初,客户端和服务器的 TCP 进程都处于 CLOSED (关闭) 状态。B 的 TCP 服务器进程先创建 传输控制块 TCB ,准备接受客户进程的连接请求。然后服务器进程处于 LISTEN (收听) 状态,等待客户的连接请求。A 的 TCP 客户进程也是首先创建 传输控制块 TCB

第一次握手 : 客户端打算建立连接时,向服务器发出连接请求报文段,此时首部中的同步位 SYN = 1 ,同时选择一个初始需要 seq = x 。 TCP 规定,SYN = 1 的报文段 不能携带数据,但要消耗掉一个序号。这时,TCP 客户进程进入 SYN-SENT (同步已发送)状态。

第二次握手:服务器收到连接请求报文段后,如果同意建立连接,则向客户端发送确认。在确认报文段中应把 SYN 位 和 ACK 位 都置 1 ,确认号是 ack = x + 1,同时也为自己选择一个初始序号 seq = y。(这个报文段也不能携带数据,但同样要消耗掉一个序号。)这时 TCP 服务器进程进入 SYN-RCVD (同步收到) 状态。

第三次握手:客户端收到服务器的确认后,还要向服务器给出确认。确认报文段的 ACK 置为 1 ,确认号 ack = y + 1,而自己的序号 seq = x + 1 。这时, TCP 连接已经建立,客户端进入 ESTABLISHED (已建立连接) 状态。当 服务器 收到 客户端 的确认后,也进入 ESTABLISHED (已建立连接) 状态。

通过这样的三次握手,客户端与服务器端建立可靠的双工的连接,开始传送数据。三次握手的主要目的是保证连接是双工的,可靠更多是通过重传机制来保证的。

为什么 客户端 最后还要发送一次确认?

主要是为了防止已失效的连接请求报文段突然又传到了 服务器,因而产生错误。 

现假定出现一种异常情况,即 客户端 发出的第一个连接请求报文段并没有丢失,而是在某些网络结点长时间滞留了,以致延误到连接释放以后的某个时间才到达 服务器。本来这是一个早已失效的报文段。但 服务器 收到此失效的连接请求报文段后,就误认为是 客户端 又发出一次新的连接请求。于是就向 客户端 发出确认报文段,同意建立连接。假定不采用报文握手,那么只要 服务器发出确认,新的连接就建立了。由于现在 客户端 并没有发出建立连接的请求,因此不会理睬 服务器 的确认,也不会向 服务器 发送数据。但是 服务器 却以为新的运输连接已经建立了,并一直等待 客户端 发来数据。服务器 的许多资源就这样白白浪费了。 采用三次报文握手的办法,可以防止上述现象的发生。假如在刚才的异常情况下,客户端 不会像 服务器 的确认发出确认。服务器 由于收不到确认,就知道 客户端 并没有要求建立连接。

四报文握手

数据传输结束后,通信的双方都可以释放资源。此时 客户端 和 服务器 都处于 ESTABLISHED 状态。



第一次握手 : 客户端 的应用进程先向其 TCP 发出连接释放报文段,并停止在发送数据,主动关闭 TCP 连接。客户端把连接释放报文段首部的终止控制位 FIN 置 1,其序号 seq = u ,它等于前面已传过的数据的最后一个字节的序号加 1 。这时 客户端 进入 FIN-WAIT-1 (终止等待1) 状态,等待 服务器 的确认。( FIN 报文段即使不携带数据,它也消耗掉一个序号。)

第二次握手:服务器收到连接释放报文段后即发出确认,确认号 ack = u + 1 ,而这个报文段 自己的序号是 v ,等于 服务器 前面已经传过的数据的最后一个字节的序号加 1 。然后 服务器 就进入 CLOSEWAIT(关闭等待)状态。TCP 服务器进程这时应通知高层应用进程(不确定自己是否还有数据要发送给 客户端(所以是四次不是三次)),因而从 客户端 到 服务器 这个方向的连接就释放了,这时的 TCP 连接处于 半关闭(Half-close)状态,即 客户端 已经没有数据要发送了,但 服务器 若发送数据,客户端仍要接收。也就是说, 服务器 到 客户端 这个方向的连接并未关闭,这个状态可能会持续一段时间。

客户端 收到来自 服务器 的确认后,就进入 FIN-WAIT-2(终止等待2) 状态,等待 服务器 发出的连接释放报文段。

第三次握手:若 服务器 已经没有要向 客户端 发送的数据,其应用进程就通知 TCP 释放连接。这时 服务器发出的连接使用报文段必须使用 FIN = 1。现假设 服务器 的序号为 w(在半关闭状态 服务器 可能又发送了一些数据)。服务器还必须重复上次已发送过的确认号 ack = u + 1。这时 服务器 就进入 LAST-ACK (最后确认) 状态,等待 客户端 的确认。

第四次握手:客户端 在收到 服务器 的连接释放报文段后,必须对此发出确认。在确认报文段中把 ACK 置 1,确认号 ack = w + 1,而自己的序号是 seq = u + 1(根据 TCP 标准,前面发送过的 FIN 报文段要消耗一个序号)。然后进入到 TIME-WAIT(时间等待) 状态。请注意,TCP 连接现在还没有释放掉。必须经过 时间等待计时器(TIME-WAIT)设置的时间 2MSL 后,客户端 才进入到 CLOSED 状态。时间 MSL 叫做 最长报文段寿命,RFC 793 建议设为 2 分钟。

为什么 客户端 在 TIME-WAIT 状态必须等待 2MSL 的时间呢?

第一,为了保证 客户端 发送的最后一个 ACK 报文段能够到达 服务器。这个 ACK 报文段有可能丢失,因而使 服务器 收不到确认。服务器 会超时重传这个 FIN + ACK 报文段,而 客户端 就能在 2MSL 时间内收到这个重传的报文段。接着 客户端 重传一次确认,重新启动 2MSL 计时器。最后,客户端 和 服务器 都能正常进入到 CLOSED 状态。如果 客户端 在 TIME-WAIT 状态不等待一段时间,而是在发送完 ACK 报文段后立即释放连接,那么就无法收到 服务器 重传 的 FIN+ACK 报文段,因而也不会再发送一次确认报文段。这样,服务器 就无法按照正常步骤进入 CLOSED 状态。

第二,防止“已失效的连接请求报文段” 出现在本次连接中。客户端 在发送完最后一个 ACK 报文段后,再经过 2MSL,就可以使本次连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。

如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP 设有一个 保活计时器。服务器每收到一次客户的数据,就重新设置保活计时器,时间的设置通常是两小时。若两小时没有收到客户的数据,服务器就发送一个 探测报文段 ,以后则每隔 75 秒钟发送一次。若一连发送 10 个探测报文段后仍无客户的响应,服务器就认为客户端出了故障,接着就关闭这个连接。


参考:《计算机网络》谢希仁