TCP的三次握手已经说烂了,TCP为何要三次握手?为何不两次握手也有很多说法。对于这些类似的问题,最好的办法是看RFC

常规思路,由面到点

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
  • 两军问题

在不可靠通信下,两军想要达到状态一致是无解的。因为在不可靠信道下,一边状态的确认需要另一边的回复(ACK),而另一边回复时再次面临不可靠信道问题,这样就回到了问题的最初,无限递归

既然“两军问题”无解,TCP也面临此问题,为何TCP还能可靠传输数据呢?

两军A,B为达成一致状态,A需要知道B是否收到信息(A-->B),B需要知道A是否知道B已经收到信息(B回复ACK)(B-->A)

但对于TCP,A只需要知道B有没有收到信息,并不关心B是否知道A已经知道B已经收到信息(这里语言表达不太友好...),只要网络不是100%不可靠,这个是可以实现的(例如通过超时重传机制辅助)。即:TCP并不需要两边状态一致

感觉有人要打我脸了,用过钉钉的人会说,A发送给B一条消息,B看到后会显示“已读”,你看,在不可靠的网络上这不状态一致了么?额。。。

这里有两个惯性思维

1.一般用户甚至程序员会理所当然的认为网络,通信软件是可靠的(这个只能说国内的公网还不错),认为B收到A的消息并且看了以后,A那端一定会显示“已读”

2.上帝视角。A和B是同时在你的脑袋里虚拟的角色,B通过你这个上帝得知了A的情况而不是通过“不可靠信道”

 

这是环境使然,打破这个只能靠逻辑,或者换一个环境

我们换一个环境:

在战区,电磁干扰很严重,A给B发送了一条消息,虽然A端显示已读,但这时候B就懵逼了:A有没有收到?(还深信不疑只要B看了A就一定收到且显示为“已读”么?)

当然此时很多人还是会质疑我:平时约朋友,A发消息:晚上5.见,B回消息:好,5.见。这事就定了啊,实际也就是这样约到的啊?!。。。

这里的问题在于:

1.国内的公网大多数情况下是可靠的,尤其对于IM这种消息量不大的应用体现更为明显,所以给人造成刻板印象,A发出去的B一定能收到,A也一定能收到B给的ACK  

2.事情本身的风险很小(能容忍的不确定性很大)

 

我们用“笨方法”分析

A ----> B  (A向B发送消息:晚上5.见)

B ----> A  (B收到消息回复ACK

A收到ACK并显示“已读”,此时A,B各自的状态为:

A:知道B收到了消息

B:不知道A是否收到ACK,即:不知道A是否知道B收到消息“晚上5.见”

 

设定一个场景,A,B必须同时在5.约定的地点出现,否则单方出现的会被出卖,即:任何一方都不能冒险,必须100%确定

由于B 不知道 A 是否知道B已经收到消息,所以没法100%确定,所以B不会赴约

A也知道B会这么想,所以A也不会赴约

继续:

B ---->  A   (B向A发送消息:好,5.见)

A ---->  B   (A向B发送ACK)

B收到ACK并显示“已读”,此时A,B各自的状态为:

A:不知道B是否收到ACK,即:不知道B是否知道A收到消息“好,5.见”

B:知道A收到了消息

 

综合上面的情况:

A:确定:B收到了消息“晚上5.见”,确定:B不确定A知道B收到消息“晚上5.见”,不确定:B知道A收到消息“好,5.见”,

B:确定:A收到了消息“好,5.见”,确定:A不确定B知道A收到消息“好,5.见”,不确定:A知道B收到消息“晚上5.见”

此场景“不确定”会被认为“没有,不会,没发生”

A的视角:B收到了消息“晚上5.见”,B会认为我这边显示发送消息“晚上5.见”失败,B认为:我会认为B显示发送消息“好,5.见”失败

B的视角:A收到了消息“好,5.见”,A会认为我这边显示发送消息“好,5.见”失败,A认为:我会认为A显示发送消息“晚上5.见”失败

这里最容易脑卡的地方在:

1.B确实收到了A发的消息“晚上5.见”,A也知道B收到了这个消息,但从B的角度,B无法确定A收到了ACK,所以只能认为A没有收到ACK,从而认为A显示发送消息“晚上5.见”失败

2.B是否赴约取决于B认为A是否会赴约,当前的不确定性和风险让B认为A显示发送失败,从而认定A不会赴约

在不可靠背景下,发送ACK的一方都会认为对方没有收到ACK,从而认为对方会认为对方自己之前发送的消息失败,无法达到一致

我们再看,A会赴约么?A赴约与否取决于从A的视角看B会不会去赴约

A的视角认为:B会认为我这边显示发送消息“晚上5.见”失败,B认为:我会认为B显示发送消息“好,5.见”失败

简单的说就是:B认为我没法消息“晚上5.见”,B也会认为:我认为B没法消息“好,5.见

A,B就是两个杠精,相互否认。。。谁敢赴约?。。。

 

  • ISN

如果是两次握手,B收到A的SYN包后状态即为:ESTABLISHED,此时B再给A发送一个SYN+ACK包,由于此时B已经为ESTABLISHED,所以可以向A发送数据,如果数据包先于SYN+ACK包到达A,A就尴尬了,A没有收到B的ISN,没法判断这些数据包是否能用(< ISN 该直接丢弃),一个优雅牛逼的协议是没法容忍这些的

如果是三次握手,B收到A的SYN包后状态即为:SYN_RECV,是没法发送包的

但有一点是相同的,无论是SYN_RECV或者ESTABLISHED,都是一种等待状态,都需要发送数据包确认对方是否还在(SYN_RECV会出现SYN flood)

你可以设计两次握手,但相应的状态机也需要重新设计,TCP目前的状态机仅仅是多种设计方案中的一种,真要谈两次握手需要重新设计协议,在协议内部(尤其是状态机已经确定)谈为何不两次握手意义不大,毕竟这仅仅是整个协议的一部分,此时需要的是从整个协议的角度思考

 

  • 四次握手

三次握手在机制上本质是四次握手的简化

1.A ----> B (SYN,ISN)

2.B ----> A  (ACK)

3.B ----> A  (SYN,ISN)

4.A ----> B  (ACK)

2和3合并后成了“三次握手”,四次握手建立连接和四次握手关闭连接是对应的,体现了全双工

 

更多次握手能从整体上衡量出在一个时间段内网络是否可靠,握手次数要求越多,对那段时间的可靠性要求也高,但从工程上没有太大必要

1.TCP是兼顾效率和可靠性的协议

2.网络的稳定性存在随机性,也许开始很好,接下来就拥塞了,此前的握手再流畅也是然并暖

握手并不是为了处理可靠性问题

这里也许会刷新你对习以为常的网络是可靠的惯性思维,如今人们把状态一致的重任交给了依靠公网的IM,刻板印象中也没出过错,但大概率不出错不表示100%不出错,从理论的完备性看,IM无法担当状态一致的重任

但我们何尝又不是活在一个不确定的世界中,云服务SLA最多给你6个9,银行金融系统都不是100%可靠,电力系统,飞机,卫星,火箭,高铁都是这样,但我们依旧活的似乎“很确定”

习惯了大概率的确定性,面对真实的不确定时会手足无措,难以接受

习惯了不确定性,面对大概率的确定性时,感叹的可能是一种难以描述的舒适和震感

 

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄