TCP 为什么是流协议而不是包协议
旧事重提的意义不是为了重复解释,而是为了以史为鉴,鉴别新事的方向和对错。
TCP 为什么是流协议?还是要从早期文献中寻找答案:The design philosophy of the DARPA internet protocols。
作为定论的 TCP 流协议并非一蹴而就,早期的争议持续了好几年,最终定稿时被确认为 TCP 流,事后来看,总结 4 点原因:
- 作为字节流的协议允许将控制信息插入字节序列空间,并和有效数据一样期望得到确认,比如 SYN,FIN。早期协议选择字节流而非数据包,考虑到 1970 年底啊的硬件,实现必须简单。
- 无边界字节流允许将 TCP 载荷在需要的时候拆分合并成任意大小,以适配早期非常异构的网络。IP 尚未从 TCP 分离,作为整体的 TCP 底层逻辑需要包含分包(即分离后的 IP 分片)功能。
- 早期应用以远程登录和文件传输为主要两类,为统一处理块数据和单字符的传输,字节流作为交集更具适应性,当主机为远程登录产生大量单字符报文时,考虑到早期网络狭窄的带宽,合并重传多个连续丢失的报文更容易抵抗再次丢包并能减少传输报文数量缓解拥塞。
- 早期主机内存和网络带宽有限,大粒度流控会增加抖动,选择数据包作为协议单位将严重影响吞吐率,如果 receiver 指定包数而不是字节数,实际接收的数据量将相差千倍,一个数据包里可以放 1 个字节,也可以放 1000 个字节,这对拥塞抖动的影响也很大。选择字节而非包,算一种洪流和抖动间的权衡。
在此四个基础之上,TCP 正式开始构建,我们今天可以理解,这四个原因其实来自于一个根本,即 IP 没有单独分离出来。
我们现在已经习惯了 IP 协议,但如果没有 IP,TCP 就不得不把后来属于 IP 的职责实现,同时对自己的核心职责做权衡取舍,这就是早期争议的核心。IP 自 TCP 分离以后,传输层作为纯端到端逻辑就干净了,但 TCP 几乎和 IP 一起被标准化(RFC791 和 RFC793),已容不得修改,这对后期 TCP 的应用和迭代造成了巨大的麻烦。该麻烦从 1984 年 Nagle 的 RFC896 对拥塞控制的质疑开始,直至今日。
现在我们从相反的方向来看 TCP 作为字节流协议与应用层的相互作用,主要强调应用层对 TCP 本身的影响。
1970 年代设计 TCP 的背景很清晰,希望用该协议承载尽可能多的应用,由于当时的应用类型非常有限且简单,TCP 将本应该由应用本身负责的职责随手实现就是举手之劳,现在我们发现,从早期的 EOL(End-Of-Letter) 到 PSH,URG,以及 4 次挥手的过程,都属于过度设计,反而增加了复杂性引起了麻烦。
有人咨询 FIN-Wait-2,Last-ACK 时,我的回答永远是 “直接 RST 掉连接”。
引起这些麻烦的原因和 IP 造成的结果如出一辙,早期 TCP 将上层和下层的逻辑统一到了自身,成了黏合层,以至于到了后来即使上下层独自发展,TCP 仍然携带着设计时的包袱。
总结一下,TCP 为什么是作为字节流的 TCP:
- 适应 1970 年代的主机和网络硬件和软件,慢处理器,小内存,低带宽,固定应用;
- 设计模糊,缺乏洞察力,未以一种足够普通的通用方式实现 TCP,弄巧成拙。
回顾 TCP 并非要认真学习 TCP,也并非只为讲个故事,而是促进审视我们的当下,是否仍然在做相同的事情。在设计新传输协议,在纠结它的形式时,回顾 TCP 的历史,我们是不是踩进了同一个坑里。设计专有承载协议时,有没有为了通用而舍弃性能,设计通用承载协议时,有没有为了性能而陷入胶着,有没有在 CPU 上优化专有协议,在硬件上 offloading TCP。一个举手之劳可能会背上一个生命周期的包袱,一个舍不得或许就再也丢不掉,兜兜转转。
作为 重新设计 TCP 读后感。
浙江温州皮鞋湿,下雨进水不会胖。