大白话告诉你 TCP 为什么需要三次握手四次挥手

Hello 大家好,我是阿粉,关于 TCP 的三次握手和四次挥手相比大家早就烂熟于心了,毕竟这也是一个高频的面试题,但是很多小伙伴只是照本宣科,并没有真正的理解其中的原理,这篇文章,阿粉用通俗易懂的大白话带你们重新熟悉一下,已经掌握的小伙伴可以在回味一下,没有掌握的小伙伴刚好可以查漏补缺。

三次握手

再解释三次握手之前我们先从生活中想一个场景,假如你现在需要给领导打个电话汇报一下工作,为了保证双方能正常交流,那么在电话接通的那一刻想象一下会不会是这样的场景。

我们:领导好,我是 xx,可以听到我说话吗?有个工作需要向您汇报下。

领导:嗯,可以的,能听到我说话吧。

我们:可以。

我们:…..(汇报工作)。

通过上面的场景我们可以看出,其实跟 TCP 的三次握手是一个意思,只不过在我们日常生活中并不是每次都这么严谨,但是 TCP 不一样,TCP 是通过程序实现的,可靠的,面向连接的协议。而且程序是严谨的,每一次的建立连接都会进行这样的步骤。放一张经典的 TCP 三次握手图。

我们先用大白话解释一下为什么需要三次握手,首先我们要知道建立连接的目的是什么,我们是为了可靠的传输数据。那既然是可靠的传输数据,我们必须保证客户端和服务端都能正常的发送和接收数据,如果某一方不能正常的发送或者接收数据,那整个数据的传输就不能成功也就不可靠了。知道这个我们看下,为什么会需要三次握手,而不是两次握手。

  1. 刚开始客户端和服务端都是处于关闭的状态,而且服务器 B 端一直处于监听的状态,时刻监听是否有建立连接的请求;
  2. 当有客户端需要建立连接的时候就会发送一个确定连接的报文,此报文是同步报文SYN = 1,并且会生成一个随机的序号 seq = x,这是第一次握手;
  3. 当服务端接收到请求连接报文的时候,会发送一个同步报文确认报文,此报文 SYN = 1,并且 ACK = 1,同时服务端也会随机生成一个 seq = y,并将 ack 设置成 x + 1,回传给客户端,这是第二次握手;
  4. 当客户端接收到服务端的 ACK 报文后,会回复一个 ACK 确认报文,用于确认确认报文已经收到,此报文 ACK = 1,seq = x + 1, ack = y + 1,这是第三次握手;

这里有个点说明一下:大写的 ACK 表示报文的类型是确认报文,小写的 ack 是报文里面的确认号,这个确认号是上一次握手对方的 seq 值加 1 得到。

上面是整个三次握手的过程,现在我们分析一下为什么三次握手可以可靠的确定客户端和服务端都能支持的发送和接收数据。

第一次握手:第一次握手是客户端发送同步报文到服务端,这个时候客户端是知道自己具备发送数据的能力的,但是不知道服务端是否有接收和发送数据的能力;

第二次握手:当服务端接收到同步报文后,回复确认同步报文,此时服务端是知道客户端具有发送报文的能力,并且知道自己具有接收和发送数据的能力,但是并不知道客户端是否有接收数据的能力;

第三次握手:当客户端收到服务端的确认报文后,知道服务端具备接收和发送数据的能力,但是此时服务端并不知道自己具有接收的能力,所以还需要发送一个确认报文,告知服务端自己是具有接收能力的。

当整个三次握手结束过后,客户端和服务端都知道自己和对方具备发送和接收数据的能力,随后整个连接建立就完成了,可以进行后续数据的传输了。

看到这里,如果大家理解了就会知道很明显,两次握手是不行的,因为服务端并不知道客户端是具备接收数据的能力,所以就不能成为面向连接的可靠的传输协议。就像我们上面提到的打电话的例子,也是为了双方能够正常的进行交流,只不过我们现实生活中不会那么严谨,并不是每次都这样,但是程序是不一样的。

四次挥手

三次握手是为了建立可靠的数据传输通道,四次挥手则是为了保证等数据完成的被接收完再关闭连接。既然提到需要保证数据完整的传输完,那就需要保证双方都达到关闭连接的条件才能断开。

img

从上图中我们可以看到,

  1. 客户端发起 FIN 断开连接的报文,携带随机生成的 seq 值 u,发送给服务端,并且自己处于 FIN-WSIT 状态,这是第一次挥手;
  2. 服务端接收到 FIN 报文后,回复一个确认报文,其中 ACK = 1,随机生成一个 seq,以及 ack = u + 1,这是第二次挥手;
  3. 当服务端数据发送完了过后,再发送一个 FIN 报文给客户端,通知客户端,服务端准备关闭连接了,此报文 FIN = 1,ACK = 1,ack = u + 1,seq = w,这是第三次挥手;
  4. 当客户端收到 FIN 确认报文时再发送一个FIN 的确认报文,其中 ACK = 1,seq = u + 1,ack = w + 1,并进入 TIME-WAIT 状态,当等待 2MSL 后关闭连接,这是第四次挥手。

第一次挥手客户端发起关闭连接的请求给服务端;

第二次挥手:服务端收到关闭请求的时候可能这个时候数据还没发送完,所以服务端会先回复一个确认报文,表示自己知道客户端想要关闭连接了,但是因为数据还没传输完,所以还需要等待;

第三次挥手:当数据传输完了,服务端会主动发送一个 FIN 报文,告诉客户端,表示数据已经发送完了,服务端这边准备关闭连接了。

第四次挥手:当客户端收到服务端的 FIN 报文过后,会回复一个 ACK 报文,告诉服务端自己知道了,再等待一会就关闭连接。

疑问

  1. 为什么握手要三次,挥手却要四次呢?

    那是因为握手的时候并没有数据传输,所以服务端的 SYN 和 ACK 报文可以一起发送,但是挥手的时候有数据在传输,所以 ACK 和 FIN 报文不能同时发送,需要分两步,所以会比握手多一步。

  2. 为什么客户端在第四次挥手后还会等待 2MSL?

    等待 2MSL 是因为保证服务端接收到了 ACK 报文,因为网络是复杂了,很有可能 ACK 报文丢失了,如果服务端没接收到 ACK 报文的话,会重新发送 FIN 报文,只有当客户端等待了 2MSL 都没有收到重发的 FIN 报文时就表示服务端是正常收到了 ACK 报文,那么这个时候客户端就可以关闭了。

总结

TCP 协议是面向连接的可靠的传输层协议,它的拥塞控制,失败重传等机制在互联网数据传输中是不可或缺的。当下互联网行业中,基于 TCP 实现的程序数不胜数。对于我们程序员来说,很多时候如果不是参与底层项目的话,很少有机会会主动编写 TCP 相关的代码,但是理解 TCP 的实现原理,对我们来说是很有帮助的。

Java Geek Tech wechat
欢迎订阅 Java 极客技术,这里分享关于 Java 的一切。