通信的意义是,在时间序列上满足消息传递的单向完成需求即可!通信的本质问题是确保消息传递,而不是维护一致性,一致性应该由业务自身来负责,通信仅仅提供消息传递的基础设施而已。
这便大大削弱了两军问题的强约束。基于上述的假设,我们来一步步地推导出TCP协议为什么要这么设计。
如果仔细推敲的话,你会发现,即便是消息传递,在数学上也是无法确保在不可靠的信道上确保消息传递的,然而,我们换个思路,即自问“信道到底不可靠到什么程度?”。
是100%不可靠吗?如果是的话,意味着断路,即双方是不可达的,无论我们发送多少次数据包,均会丢失,这样我们马上可以结束这个没有意义的讨论,因此,所谓的不可靠只是说信道会出现概率性丢包,丢包概率pp一定是介于开区间(0,1)(0,1)之间的!
这个意义十分重大,这意味着,只要我们重试特定消息MnMn的次数足够多,就一定能收到来自对端针对消息MnMn的确认!,这是完全确定的一个结论,没人反对吧。
这边自然而然导出了可靠通信的第一个原则:
该原则可以确保消息一定能有机会到达对端。每当发出一个数据包,在预期的时间内没有确认到达,就重传它。关于超时重传的细节,本文稍后会浅谈一下,但是现在,我们来看另外一个问题。
如何确保消息单向传递的完成?
换句话说,所谓消息单向传递的完成,即需要一种标志性的信号*,该信号揭示了消息已经被对端接收这个事实,很显然,对端发送针对特定消息的确认并且本端收到即可。
一旦AA收到了来自BB针对MnMn的确认,对于AA而言,它知道BB肯定收到了MnMn,而对于BB而言,它也确实收到了MnMn,不然它也不会发送确认。但是由上文可知,这个确认在不可靠的信道上也可能丢失,不过这不必惊慌,因为我们已经有了推论,即针对任意消息,只要我们重复传输的次数足够多,该消息就一定能到达对端,在该推论下,采用超时重传原则即可。
现在看来,我们导出的下列措施已经解决了几乎所有问题:
1. 针对消息MnMn的超时重传机制
2. 针对消息MnMn的确认AMnAMn的超时重传机制
但是这是最优解吗?
非也!这只是一种可行的方案,但不是唯一的方案,更不是最有的方案。导出最优解需要我们深入到通信网络的本质,先看一篇文章:
马太效应/幂律分布的本质以及其数学表述:http://i8k.wikidot.com/
注意,我们的通信网络是一个网状拓扑的连通图,无论是单节点连接数属性还是流量属性均符合幂律规律,从双对数坐标曲线可以看出网络规模和节点的各属性特征之间的对数线性关系,而网络规模来自于某种指数级增长的复制,单节点的属性特征来自于该节点的行为,很显然,在这个双对数坐标下线性的通信网络中,如果想等比例地缩放其规模而不至于崩溃,就必须用指数来控制单节点的行为(把双对数坐标化为笛卡尔坐标即可展现)。
实际上,我们把双对数坐标中的直线(求解微分方程的结果)展开到相应的笛卡尔坐标系,就是一条指数规律的曲线了。
再看另一个抽象,即如果数据包在传输过程中丢失了,这件事跟什么因素相关?诚然,在网络通信中,这件事肯定有可能是和传输介质相关的,但是在节点数量,即网络规模这个因素下,介质的问题可以忽略不计。也就是说,节点越多,传输越容易发生冲突,数据也就越不容易到达对端。即丢包事件和网络规模相关,网络是一个线性系统,所以,丢包的重传必须具备指数级的时间特征。
介质的问题随着网络规模的扩大是线性增长的,而传输冲突的问题随着网络规模的扩大则是指数级的,孰重孰轻,立判!
如果你了解早期的以太网,即总线式的CSMA/CD以太网,你会发现同样的事实。
因此,很明确,超时重传的超时规则在线性系统的平衡通过指数特性的单独节点行为来维持的原则下,则必须是:
有了这个原则,我们再回过头来看如何实现消息以及消息确认的超时重传。直接说结论,即不对确认进行重传,因为确认和消息本身属于同一个行为,针对消息本身的超时重传已经自动包含了一个确认,如果再针对确认进行重传,就会破坏单点行为的指数特征,因此我们导出可靠通信的第三个特征:
由于我们仅仅想确保消息单向传递的可靠,即确保对端收到了本端发出的消息而无需让对端知道这件事,第四个特征也随即导出:
基础设施构建就此完毕,考虑到通信往往是双向的,我们需要在其上构建一个可靠的双向通信协议,怎么办?
简单,在另一端BB重新这么来一遍即可!于是我们观察到,两军问题如果在超时重传的前提下将双向的消息传递和确认分解成两个单向的消息传递和确认,事情就会简单得多。
嗯,转换后的解法,即我们熟悉的协议,TCP协议的最基本形式。现在进入TCP时间!
经常有人问,TCP为什么是3次握手,而不是2次,也不是4次,5次。知乎上经常会有这种问题,但是答案几乎是千篇一律的错误或者答非所问,最常见的答案只是描述一下TCP握手的细节,然后导出这么做是OK的,其实不这么做也是OK的这一点没人提。
最常见的错误答案:
1. 这是一种权衡,因为无数次握手也不可能完全可靠;
2. 描述握手的协议细节;
3.http://ot8.wikidot.com/
看过了我上面的论述,这个问题应该非常好答了,所谓的TCP建立连接的握手,实质上就是建立一个双向的可靠通信连接,一边一个来回,每一边都自带超时重传来确保可靠性(而不是靠握手的次数)。TCP的3次握手是优化的结果,其实它应该是4次握手,由于是从零开始的建立连接,因此将SYN的ACK以及被动打开的SYN合并成了一个SYN-ACK,仅此而已。
握手的作用,旨在确定两个双向的初始序列号,TCP用序列号来编址传输的字节,由于是两个方向的连接,所以需要两个序列号,握手过程不传输任何字节,仅仅确定初始序列号:
说完了3次握手,那么,其姊妹问题,为什么TCP的断链是4次挥手而不是3次?
换句话说,即是在问为什么针对主动断开方的FIN的ACK以及本端的FIN不能合并?
非常简单,因为TCP是在一个单向可靠通信系统基础上构建而成的双向传输控制协议,握手期间可以合并ACK和SYN,是因为在握手之前两端没有任何连接上的包袱,而在断链挥手时,一端认为可以断开了,另一端却不一定,可能另一端还有数据要传输,所以便不能合并,被动关闭的一方只能单独处理针对FIN的ACK以及自己的FIN,仅此而已。
再来一个问题,TCP能确保一致性吗?换句话说,TCP协议是两军问题的一个解吗?
远远不是!TCP并不确保一致性。
任何时间点,TCP都不能完全确认当前时刻连接双方的状态,此处所谓的状态包括两端传输的数据。一致性是基于消息的,而不是基于连接的!也就是说,TCP只有收到下一个数据包时,才知道上一个数据包的接收情况,而无法实现隔空打人!TCP的好处仅在于,它在一个信息流上实现了一个一致性确认的流水线方式。
我们在理解这个流水线方式的时候,不应该考虑滑动窗口,那样会比较难以理解,我们应该仅仅考虑单字节停等机制。事实上也确实是这样,滑动窗口机制只是为流量控制而引入的,单字节停等效率又太低,所以说这并无伤大雅,你把字节换成窗口即可,即单窗口停等。
如果我们把一致性推广到连接的层面,在连接层面,一致性就是靠4次挥手保证的。
我们可以看到,4次挥手那里的状态机非常之复杂,这是有原因的,即便是引入了TIMEWAIT状态,也还是没有办法保证彻底的一致性,这是两军问题本质上不可解的一个结论,仅此而已。