✏️作者:银河罐头
系列专栏:JavaEE
“种一棵树最好的时间是十年前,其次是现在”
TCP 的建立连接过程(三次握手)和断开连接过程(四次挥手)
通信双方各自要记录对方的信息,彼此之间要相互认同。
举个栗子:
这个过程中的每次通信,都称为是一次"握手"(形象的比喻)
上面的例子中进行了4 次交互,但是其中有 2 次可以合并成 1 次。
总结:所谓的三次握手,本质上是四次交互。通信双方各自向对方发起一个建立连接的请求,然后再各自向对方回应一个 ack。这里其实是有 4 次信息交互,但是其中有 2 次交互是可以合并成 1 次交互的。因此就构成了三次握手。
必须合并!
封装分用 2 次一定比封装分用 1 次,成本更高。
举个栗子:
我想在淘宝上买 3 样东西
假设我是在一个淘宝店铺下单的,通过多个订单来买的,此时店家是通过 3 个快递发给我?还是 1 个快递直接发了?分 3 次发,3 次包装成本,3 次快递费;1 次发了,1 次快递成本,1 次快递费。
不行!(无法完全验证双方的发送和接收能力)
除了,通信双方要彼此认同之外,三次握手还有别的作用。
三次握手另外一个重要作用:验证通信双方各自的发送能力和接收能力是否正常。
三次握手也是一定程度的保证了 TCP 传输的可靠性。(起的是辅助作用)
举个例子:
张三和李四打游戏开黑,需要连麦
第一次交互:
当李四听到"听得到嘛",
李四就知道了:
1.张三的麦克风 ok
2.李四的耳机 ok
张三知道了:
(空)
第二次交互:
当张三听到"能听到",
张三就知道了:
1.张三的耳机 ok
2.李四的麦克风 ok
建立在第一次通信的基础上,
3.张三的麦克风 ok
4.李四的耳机 ok第三次交互:
李四听到"好了"意味着第二次通信李四说的"能听到"张三已经接收了
李四就知道了:
1.李四的麦克风 ok2.张三的耳机 ok
此时,双方都知道了彼此的麦克风和耳机都是 ok 的
连接,包含连接的建立,断开和维护,三次握手只是建立这个环节。
“有连接”
1.需要连接先建立好,才能进行通信
2.如果连接断开了,此时就无法进行通信了。
3.连接建立过程中,通信双方要保存好对方的信息。
有没有连接和是否确认应答 没有任何关系。
确认应答体现的是可靠传输,可靠传输和有连接是不相关的。
无连接,也可以可靠传输。eg:飞书/钉钉/企业微信,发个消息不需要建立连接,直接就能发,发个消息有个"已读"状态,这就相当于 ack
1.让通信双方各自建立对对方的"认同"。
2.验证通信双方各自的发送能力和接收能力是否 ok.
3.在握手的过程中,双方来协调一些重要的参数。(比如 TCP 的序号不一定是从 1 开始编号的,双方协商从哪个数字开始编号)
TCP 通信过程中,有些数据通信双方要相互同步,此时就需要有这样的交互过程,恰好可以利用三次握手的机会,来完成数据的同步。
客户端主动给服务器发起"建立连接请求",称为"syn",同步报文段
建立连接阶段,主要认识 2 个状态
1.LISTEN 服务器的状态
表示服务器已经准备就绪,随时可以有客户端来建立连接。(相当于手机开机,信号良好,随时可以有人来打电话)
2.ESTABLISHED 客户端和服务器都有的状态
连接建立完成,接下来可以进行通信了。(相当于电话打过去,对方接通了)
"挥手"和"握手"都是客户端服务器之间的数据交互。
四次挥手和三次握手非常类似。通信双方给对方发起"断开连接请求",然后再各自给对方一个回应。
两个数据发送的时机相同才能合并,不同就不能合并。
举个栗子:
我要在淘宝上买 3 样东西。
从同一个店铺买。如果这 3 个订单是同一时间下的,此时就可以一个包裹发过来,
如果是分开下的呢?
如果是今天买了 A,过了一周买了 B,再过一个月买了 C。此时就只能分成多个包裹发货了。
三次握手的中间 2 次能够合并,是因为他俩是同一时机。具体来说,三次握手 这 3 次交互过程是纯内核完成的(应用程序感知不到,也干预不了)
服务器的系统内核收到 syn 之后,就会立即发送 ack ,也会立即发送 syn
FIN 的发起,不是由内核控制的,而是由应用程序调用 socket 的 close 方法(或者进程退出),才会触发 FIN。
ACK 是由内核控制的,是收到 FIN 之后,立即返回 ACK
所以第 2 步的 ACK 和第 3 步的 FIN ,这两者之间就会隔了一个时间差,这个时间是长是短得看具体代码的实现。
之前写过的 TcpEchoServer 代码:
四次挥手中涉及到的 2 个重要的 TCP 状态
出现在被动发起断开连接的一方。
建立连接一定是客户端主动发起请求;而断开连接有可能是客户端主动发起,也可能是服务器主动发起。
举个栗子:
谈恋爱追人的时候一般是男生主动。但是分手的时候可以是男生提分手也可以是女生提分手
等待关闭(等待调用 close 方法关闭 socket)
收到 FIN 并立即返回 ACK 之后状态就设置成 CLOSE_WAIT,下一步就应该轮到应用程序调用 close 完成后续的 2 次挥手。
出现在主动断开连接的一方。
假设是客户端主动断开连接,当客户端收到 第 3 次 FIN 并 返回第 4 次 ACK 之后 TCP 就进入 TIME_WAIT 状态。
此时相当于 4 次挥手都挥完了。
此时这里的 TIME_WAIT 要保持 当前的 TCP 连接状态不要立刻就释放。
最后这个 ACK 才刚发出去,对方还没有收到呢,万一丢包了呢。
在三次握手和四次挥手的过程中也是同样存在"超时重传"的。
如果最后一个 ACK 丢包了,站在服务器的视角来看,服务器不知道是因为 ACK 丢了还是自己发的 FIN 丢了,所以统一视为 FIN 丢了,统一进行重传操作。
既然服务器可能会重传 FIN,客户端就需要针对这个重传的 FIN 进行 ACK 响应。如果刚才把连接彻底释放了这样的话 ACK 就没办法进行了。
因此使用 TIME_WAIT 状态保留一段时间,就是为了能够处理最后一个 ACK 丢包的情况,能够在收到重传的 FIN 进行 ACK 响应。
TIME_WAIT 会等,如果等了一段时间也没有收到这个重传的 FIN,此时就认为 最后一个 ACK 没丢,此时就彻底断开连接了。
约定一个时间,2 MSL
MSL 指的是互联网上,两个结点之间,数据传输消耗的最大时间。
MSL 具体是几?
通常情况下这个值是 60 s
MSL 相当于是一个经验值。按照一般的经验来说,绝大部分数据报传输的时间都不会超过 MSL
如果经历了 2 MSL 还没有收到重传的 FIN,此时就认为最后一个 ACK 成功到达了(认为对方没有重传 FIN)
TIME_WAIT 才是等这个时间,其他的交互过程都是跟着超时重传的时间阈值走的