三次握手

 TCP三次握手与四次挥手详解_数据

TCP协议中,主动发起请求的一端称为『客户端』,被动连接的一端称为『服务端』。不管是客户端还是服务端,TCP连接建立完后都能发送和接收数据。

起初,服务器和客户端都为CLOSED状态。在通信开始前,双方都得创建各自的传输控制块(TCB)。 

服务器创建完TCB后遍进入LISTEN状态,此时准备接收客户端发来的连接请求。

 

第一次握手 

客户端向服务端发送连接请求报文段。该报文段的头部中SYN=1,ACK=0,seq=x。请求发送后,客户端便进入SYN-SENT状态。

  • PS1:SYN=1,ACK=0表示该报文段为连接请求报文。
  • PS2:x为本次TCP通信的字节流的初始序号。
    TCP规定:SYN=1的报文段不能有数据部分,但要消耗掉一个序号。

第二次握手 

服务端收到连接请求报文段后,如果同意连接,则会发送一个应答:SYN=1,ACK=1,seq=y,ack=x+1。 

该应答发送完成后便进入SYN-RCVD状态。

  • PS1:SYN=1,ACK=1表示该报文段为连接同意的应答报文。
  • PS2:seq=y表示服务端作为发送者时,发送字节流的初始序号。
  • PS3:ack=x+1表示服务端希望下一个数据报发送序号从x+1开始的字节。

第三次握手 

当客户端收到连接同意的应答后,还要向服务端发送一个确认报文段,表示:服务端发来的连接同意应答已经成功收到。 

该报文段的头部为:ACK=1,seq=x+1,ack=y+1。 
客户端发完这个报文段后便进入ESTABLISHED状态,服务端收到这个应答后也进入ESTABLISHED状态,此时连接的建立完成!

 

为什么要三次握手,两次或者四次可以吗?

我们也常称为“请求 -> 应答 -> 应答之应答”的三个回合。这个看起来简单,其实里面还是有很多的学问,很多的细节。

首先,为什么要三次,而不是两次?按说两个人打招呼,一来一回就可以了啊?为了可靠,为什么不是四次?

我们还是假设这个通路是非常不可靠的,A 要发起一个连接,当发了第一个请求杳无音信的时候,会有很多的可能性,比如第一个请求包丢了,再如没有丢,但是绕了弯路,超时了,还有 B 没有响应,不想和我连接。

A 不能确认结果,于是再发,再发。终于,有一个请求包到了 B,但是请求包到了 B 的这个事情,目前 A 还是不知道的,A 还有可能再发。

B 收到了请求包,就知道了 A 的存在,并且知道 A 要和它建立连接。如果 B 不乐意建立连接,则 A 会重试一阵后放弃,连接建立失败,没有问题;如果 B 是乐意建立连接的,则会发送应答包给 A。

当然对于 B 来说,这个应答包也是一入网络深似海,不知道能不能到达 A。这个时候 B 自然不能认为连接是建立好了,因为应答包仍然会丢,会绕弯路,或者 A 已经挂了都有可能。

而且这个时候 B 还能碰到一个诡异的现象就是,A 和 B 原来建立了连接,做了简单通信后,结束了连接。还记得吗?A 建立连接的时候,请求包重复发了几次,有的请求包绕了一大圈又回来了,B 会认为这也是一个正常的的请求的话,因此建立了连接,可以想象,这个连接不会进行下去,也没有个终结的时候,纯属单相思了。因而两次握手肯定不行。(客户端、服务端历经三次握手,建立了TCP连接,并进行了通信,最终经历四次挥手断开了连接。在这个过程中,客户端请求建立连接的时候,是有可能由于网络不好,发出多个请求报文的。因此可能发生这个情况:一次连接已经结束了,这时候滞留在网络中的请求才姗姗而来到达服务端,那么服务端是无法分辨这个报文是之前重发的,现在已经没用了。如果采取两次握手,那么服务端就会返回一个ACK给客户端,此时服务端就认为连接建立了。这显然不行哈,因此规定了三次握手,像这种历史报文,客户端就不会回复~)

B 发送的应答可能会发送多次,但是只要一次到达 A,A 就认为连接已经建立了,因为对于 A 来讲,他的消息有去有回。A 会给 B 发送应答之应答,而 B 也在等这个消息,才能确认连接的建立,只有等到了这个消息,对于 B 来讲,才算它的消息有去有回。

当然 A 发给 B 的应答之应答也会丢,也会绕路,甚至 B 挂了。按理来说,还应该有个应答之应答之应答,这样下去就没底了。所以四次握手是可以的,四十次都可以,关键四百次也不能保证就真的可靠了。只要双方的消息都有去有回,就基本可以了。

好在大部分情况下,A 和 B 建立了连接之后,A 会马上发送数据的,一旦 A 发送数据,则很多问题都得到了解决。例如 A 发给 B 的应答丢了,当 A 后续发送的数据到达的时候,B 可以认为这个连接已经建立,或者 B 压根就挂了,A 发送的数据,会报错,说 B 不可达,A 就知道 B 出事情了。

当然你可以说 A 比较坏,就是不发数据,建立连接后空着。我们在程序设计的时候,可以要求开启 keepalive 机制,即使没有真实的数据包,也有探活包。

另外,你作为服务端 B 的程序设计者,对于 A 这种长时间不发包的客户端,可以主动关闭,从而空出资源来给其他客户端使用。

 

四次挥手

TCP三次握手与四次挥手详解_数据_02

TCP连接的释放一共需要四步,因此称为『四次挥手』。 

我们知道,TCP连接是双向的,因此在四次挥手中,前两次挥手用于断开一个方向的连接,后两次挥手用于断开另一方向的连接。关闭连接时,某一方发送 FIN 时,仅仅表示该方不再发送数据了但是还能接收数据。

 

第一次挥手 

若A认为数据发送完成,则它需要向B发送连接释放请求。该请求只有报文头,头中携带的主要参数为: 

FIN=1,seq=u。此时,A将进入FIN-WAIT-1状态。

  • PS1:FIN=1表示该报文段是一个连接释放请求。
  • PS2:seq=u,u-1是A向B发送的最后一个字节的序号。

第二次挥手 

B收到连接释放请求后,会通知相应的应用程序,告诉它A向B这个方向的连接已经释放。此时B进入CLOSE-WAIT状态,并向A发送连接释放的应答,其报文头包含: 

ACK=1,seq=v,ack=u+1。

  • PS1:ACK=1:除TCP连接请求报文段以外,TCP通信过程中所有数据报的ACK都为1,表示应答。
  • PS2:seq=v,v-1是B向A发送的最后一个字节的序号。
  • PS3:ack=u+1表示希望收到从第u+1个字节开始的报文段,并且已经成功接收了前u个字节。

A收到该应答,进入FIN-WAIT-2状态,等待B发送连接释放请求。

第二次挥手完成后,从A到B这个方向的连接就释放了,这时的 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。(因此第三次挥手的seq不一定是v+1)

第三次挥手 

当B向A发完所有数据后,向A发送连接释放请求,请求头:FIN=1,ACK=1,seq=w,ack=u+1。B便进入LAST-ACK状态。

第四次挥手

A收到释放请求后,向B发送确认应答,此时A进入TIME-WAIT状态。该状态会持续2MSL时间,若该时间段内没有B的重发请求的话,就进入CLOSED状态,撤销TCB,释放连接。当B收到确认应答后,也便进入CLOSED状态,撤销TCB,释放连接。

 

为什么需要第四次挥手­­

假设只有三次挥手,也就是说服务端在发送完数据就向客户端发送一个带有 FIN 信号的报文,之后服务端彻底关闭连接,但是如果这个 FIN 报文丢失了,客户端没有收到这个报文, 客户端就会以为服务端仍有数据要发送,不会关闭连接。所以我们需要第四次挥手,让客户端收到 FIN 报文后发送一个确认报文,确认自己收到了关闭的信号,这样服务端在收到这个报文后就可以彻底关闭连接了。(如果第四次挥手丢失,即服务端没有收到FIN的确认,那么会超时重发第三次挥手。如果一共只有三次挥手的话,那服务端就无法确认客户端是否收到这个FIN请求,如果FIN请求丢失,也无法重发,因为没有第四次挥手确认的过程)

 

为什么A在TIME_WAIT状态必须等待2MSL的时间呢?

客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器,设置的时间 2MSL。这么做有两个理由:

(1)确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。

(2)等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。(如果A不等待2MSL就关闭连接的话,A 的端口就直接空出来了,但是 B 不知道,B 原来发过的很多包很可能还在路上,如果 A 的端口被一个新的应用占用了,这个新的应用会收到上个连接中 B 发过来的包,虽然序列号是重新生成的,但是这里要上一个双保险,防止产生混乱,因而也需要等足够长的时间,等到原来 B 发送的所有的包都死翘翘,再空出端口来。)

 

 

参考文章

计算机网络第七版 - 谢希仁编著

​一张图秒懂三次握手​

​极客时间:趣谈网络协议,讲师:刘超,前网易研究院云计算技术部首席架构师