三次握手和四次挥手是TCP连接建立和终止过程中的步骤,用于确保数据传输的可靠性和连接的稳定性。它们分别用于建立TCP连接和关闭TCP连接。
三次握手过程:
客户端和服务器端刚开始都是处于 CLOSED(关闭)状态,要注意的是客户端主动打开连接,而服务器端是被动打开连接的。服务器端进程准备好接收来自外部的 TCP 连接,一般情况下是调用 bind、listen、socket三个函数完成。这种打开方式被认为是被动打开,任何服务器端进程处于 LISTEN 状态,等待客户端连接请求。
(Three-way Handshake)其实就是指建立一个 TCP 连接时,需要客户端和服务器总共发送 3 个包,进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号,为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。
1、第一次握手--建立连接请求:客户端通过 connect 发起主动打开,向服务器发出连接请求,请求中首部同步位 SYN=1(SYN=1表示握手请求,正常通讯的数据包 SYN=0),同时选择一个初始序列号 sequence,简写 seq=x。SYN 报文段不允许携带数据,只消耗一个序号。此时客户端进入 SYN-SEND 状态。(服务器确认自己的接收能力,和客户端的发送能力)
2、 第二次握手--确认连接请求:服务器收到客户端连接后,需要确认客户端的报文段。在确认报文段中,把 SYN 和 ACK 位都置为 1(ACK为确认序号有效位,ACK=1时,确认序号才有效)。确认号是 ack=x+1,同时也为自己选择一个初始序号 seq=y。这个报文段也不能携带数据,但同样要消耗掉一个序号。此时,TCP 服务器进入 SYN-RECEIVED(同步收到)状态。(客户端确认自己的收发能力,以及服务器的发送能力)
3、第三次握手--确认连接确认:客户端在收到服务器发出的响应后,还需要给出确认连接。确认连接中 ACK 置为 1,序号为 seq=x+1,确认号为 ack=y+1。TCP 规定,这个报文段可以携带数据,也可以不携带数据,如果不携带数据,那么下一个数据的报文段的序号仍是 seq=x+1。这时,客户端进入 ESTABLISHED(已连接)状态。服务器收到客户端的确认后,也进入 ESTABLISHED 状态。(服务器确认客户端的接收能力)
三次握手的作用:
- 确认双方的接受能力、发送能力是否正常
- 指定自己的初始化序列号,为后面的可靠传输做准备
为什么一定要三次握手而不是两次,为什么客户端最后还需要发送一次确认报文呢?其实主要是为了防止已经失效的连接请求报文突然又被传送给了服务器端,然后产生错误。假设有一种情况,客户端发出的第一个连接请求报文段并没有丢失,而是在某些网络节点上被滞留了,直到客户端和服务器端的新连接已经释放后的某个时间点,第一个连接请求报文端才到了服务器端,这时候服务器端以为客户端又发起了一次请求,于是服务器端向客户端发起了确认连接报文段,同意连接。假设不采用三次握手,这时候连接已经建立了,但是客户端并不知道这个情况,服务器端会一直等待客户端的数据报文,这样服务器端的资源就会被浪费,占有大量的资源。所以采用三次握手可以防止这种现象,保护网络和系统资源。
四次挥手过程:
TCP 连接释放的过程比较复杂,客户端和服务器端都可以主动释放连接。下面从客户端主动释放连接为例讲解四次挥手的详细过程:
1、第一次挥手--客户端请求关闭连接:客户端的应用进程先向 TCP发出一个连接释放报文段,然后停止发送数据,主动关闭 TCP 连接。客户端主动发送释放连接的报文段,报文段中首部的终止控制 FIN 置为 1,不包含数据,序号号位 seq=u(u 相当于前面传输的数据报文段的最后一个字节的序号加 1),此时客户端主机进入 FIN-WAIT-1(终止等待1)阶段,等待服务器的确认。
2、第二次挥手--服务器确认连接关闭请求:服务器主机接收到客户端发出的报文段后,即发出确认应答报文,确认应答报文中 ACK=1,生成自己的序号位 seq=v,确认号 ack=u+1,然后服务器主机进入 CLOSE-WAIT(关闭等待)状态。这时候服务器端的 TCP 进程就要通知应用进程,客户端到服务器端的连接已经关闭了,这个时候的 TCP 连接处于一个半关闭(half-close)状态,尽管客户端已经没有数据要发送了,但是服务器端还是可以向客户端发送数据的,服务器端到客户端的连接并没有被释放掉。
此时的客户端主机,在收到服务器端主机的确认应答后,即进入 FIN-WAIT-2(终止等待2)状态。等待客户端发出连接释放的报文段)。
3、第三次挥手--服务器请求关闭连接:如果服务器端也没有数据要发送给客户端了,那么应用进程就通知 TCP 释放连接。这时服务器端主机会发出断开连接的报文段,报文段中 ACK=1,序列号 seq=v,ack=u+1,在发送完断开请求的报文后,服务器端主机就进入了 LAST-ACK(最后确认)状态,等待客户端的确认。
4、第四次挥手--客户端确认连接关闭请求:客户端收到服务器端的断开连接请求报文段后,客户端需要作出响应,客户端发出断开连接的报文段,在报文段中,ACK=1,序列号 seq=u+1,因为客户端从连接开始断开后就没有再发送数据,ack=v+1,然后进入到 TIME-WAIT(时间等待)状态,请注意,这时候 TCP 连接还没有释放,必须经过时间等待的设置,也就是 2MSL 后,客户端才会进入 CLOSED 状态,时间 MSL 叫做最长报文段寿命(Maximum Segment Lifetime)。
服务器端只要收到了客户端的断开连接确认后,就会进入 CLOSED 状态。因为服务器端结束 TCP 连接时间要比客户端早,而整个连接断开过程需要发送四个报文段,因此释放连接的过程被称为四次挥。
握手为什么是三次,两次和四次不可以吗
- 确认客户端和服务器收发能力正常
我们需要首先弄明白三次握手的意义。三次握手其实就是指建立一个 TCP 连接时,需要客户端和服务器总共发送 3 个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。
第一次握手:客户端发送网络包,服务端收到了,这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收能力、发送能力,客户端的接收和发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收能力、发送能力正常,服务器自己的发送、接收能力也正常。
如果只有两次握手,那么客户端是知道了双方接收能力正常了,但是服务器端并不知道自己的发送和客户端的接收能力是否正常。因此,需要三次握手才能确认双方的接收与发送能力是否正常。
- 防止【历史连接】初始化了连接
试想如果是用两次握手,则会出现下面这种情况:
如果客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求,后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务器,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误以为客户端又发出一次新的连接请求,于是就想客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一直等待客户端发送数据,浪费网络资源。在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。
- 同步双方初始序列号(ISN)
TCP 协议的通信双方,都必须维护一个【序列号】,序列号是可靠传输的一个关键因素,它的作用:可以去重、维护接收顺序、了解哪些已被接收。
可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带【初始序列号】的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送【初始序列号】个客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠地同步。
- 为什么不是四次握手呢
三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。