从Telnet到TCP,从HTTP2.0到QUIC

前序

好久没有写软文了,这是一篇软文。

软文,在普通人眼里,就是广告,但从专业的角度看,就是没有技术含量的宣传性文章,一般都是穿西装皮鞋的经理喜欢写这种东西,其实你问他们技术细节他们根本就不懂,但是他们嘴里却能像弹珠一样吧嗒吧嗒地扯五个小时…五个小时的内容,就是软文(这是我的定义)。

然而,在中国,现实是,写软文的或者吧嗒吧嗒五个小时的人赚钱会比温州皮鞋厂老板这种纯工程师,这种talk is cheap,show me the f**king XXX要多很多,很多人仅仅靠扯淡就能财务自由,我是看不起的。我比较能看得起的就是温州皮鞋厂老板!

所以,我也写一篇软文,狠狠鄙视一下自己,顺便,告诉大家,软文谁都能写。不过,我更想说的是,在写软文之前,最好确认自己属于相关领域的第一二梯队。忠言逆耳。

QUIC来了,欢迎讨论!


正文

当几乎所有人都在关注TCP BBR算法的时候,整个故事其实还有另外一条脉络,花开两朵,各表一枝,本文将从历史发展的角度梳理故事的情节。

事情从20世纪70年代开始。

不得不说,人们最初的想法是不要让应用感知网络的存在,分层模型早已有之,网络层协议会处理网络细节。这就使得不管最终谁成了传输协议的标准,都一定要是端到端的。从业务的角度看,人们希望通过网络远程登录一台UNIX主机,这也就使得Telnet成了TCP/IP体系下最为古老的协议之一,一直到今天,我们依然在使用它(虽然现在它已经退化成端口测试工具了)。

我们来看看Telnet这一类程序的特点:

  • 远程输入必须到达主机,一个字节都不能丢;
  • 远程输出必须到达终端显示,一个字节都不能丢;
  • 输入的顺序必须和主机接收的顺序一致,不能乱序;
  • 输出的顺序必须和终端接收的顺序一致,不能乱序。

这写特征可以总结为强时序依赖,你很难跳过一些步骤提前做以后的事情,因为未来的输出依赖于此前的输入。这注定未来承载Telnet的协议需要构建的是一个双向串行流

为了在一个不可靠的网络中构建端到端的可靠的双向串行流,主机需要做什么以及怎么做?

从资源的角度来看,20世纪70年代是一个资源匮乏的年代,无论是带宽还是内存,如果说网络是不可靠的,为了在主机端保证可靠性,排队论告诉我们这注定会让主机的内存利用率指数级膨胀,最终系统崩溃。

另一方面,请了解一下停等协议,这应该是最简单的实现上述可靠按序需求的主机端方案(端到端需求)了,嗯,就是它了!

人们很自然地会从停等协议得到扩展,如果能1个字节停等,那就能2个字节停等,那为什么不是n个字节停等?因此,这注定了所谓的TCP协议实现所具有的超级特征,即:

  • n字节停等,n字节积累确认;
  • n字节滑动窗口,资源限制;
  • 基于数据包的超时重传。

非常完美!这就是TCP!积累确认和滑动窗口是其基本特征,其它的都是后面加入的,请注意,一开始的时候并没有拥塞控制,这是一个非常值得注意的点。我来从反面解释一下。

如果TCP一开始就考虑了拥塞控制,它可能就不会选择积累确认,而是会采用完全选择确认的方式,而我们知道TCP的SACK是在迭代了很多版本后才专门为更精确的进行拥塞控制引入的,注意到这个因果关系将会对优化拥塞控制算法有很大的帮助。

积累确认是一种非常简单的实现方式,只要有按序到达的数据包便迅速消费掉,非按序到达的数据包到达就直接丢弃(再次重申:TCP的原始版本没有SACK),这反过来满足了主机内存资源匮乏时代的限制性要求,然而,这种简单的实现方式注定使得TCP发送端在收到积累性确认后,无法区分该确认是针对原始数据包的还是针对重传数据包的,这个问题将会对未来所有的拥塞控制算法相关的RTT测量带来极大的困扰!


故事讲到这里,我们发现TCP完美满足了Telnet这种程序的需求,如果把坐在Telnet终端前的人换成另一个程序,事情依然完美,但这是另外一个故事,BSD socket是故事的主角,这使得BSD socket成为了IPC的常规方式。

在Telnet之外,文件传输是另一个典型业务场景,总之,我们可以把TCP总结为可以把一块数据从一个地方原封不动搬运到另一个地方的协议


1989年开始衍生出来的HTTP0.9是一个请求文件的协议,它向服务器请求一个文件,随即服务器将文件会送给请求者。这像极了Telnet,即我输入一个ls,然后主机将目录内容列表显示给我。这也注定了一开始HTTP就会选择TCP作为它的承载协议。

事情也许就会这样结束,因为HTTP和TCP搭档实在是太成功了,TCP载着HTTP度过了HTTP的童年。也许TCP会一直这么载着HTTP…


事情悄悄地起了变化…


进入青春期后的HTTP1.x使得互联网发生了重大的变化,互联网突然显得热闹了起来,Web浏览器的出现和发展让互联网应用的交互性得到加强,同时页面也更加漂亮了,这些反过来又催生了更多更好玩更有用的互联网应用。

当人们输入一个URL点击回车的时候,已经不再仅仅是GET一个文件这么简单了,你会发现实际上可能发出了100个请求,每一个页面上小小的gif可能都是一个文件,就更不要说那些复杂的CSS,js脚本之类的了。

事情就发展了10多年,大概从20世纪90年代末到2010年左右吧,苹果公司iPhone的发布和成功让移动互联网开始发展,作为互联网本身的衍生,移动互联网依然采用了已经非常成的HTTP作为其应用的承载,而HTTP依然使用TCP作为其数据的承载(事实上,没有标准规定HTTP一定必须使用TCP来承载)。

然而,之前在PC端发生的故事并没有在手机端延续下来,这让人不禁感叹,不是我不明白,这世界变化快。各种资源逐渐丰盈起来,CPU和内存价格不断降低,使得一部千元智能手机的性能可以秒杀十几年前的PC,而带宽资源的丰盈以及资费的逐渐降低使得用3G,4G网络在路上在车上看视频成了一种时尚,现如今,抖音段视频这是迎合了这股波峰。

旧瓶装新酒还可能吗?

可能已经不适合了。相比Telnet和简单文件传输,我们现在面对很多问题:

  • 音视频业务,只要关键帧不丢太多,是允许丢包的,滑动窗口并不必要;
  • 同一个URL回传的文件太多,TCP丢包会造成滑动窗口阻滞,队头拥塞;
  • 手机等移动终端的IP地址会频繁变更,造成TCP的断线重连;
  • Web页面交互和文件传输以及视频播放同时进行;

这并不怪TCP,因为TCP天生就不是为多路复用场景而设计的。

是要革命的时候了。

不过这一次,Google的做法是直接从应用本身入手,这就催生了HTTP2.0,作为一个大版本的升级,HTTP2.0相比HTTP1.x而言的变化可不是一点两点,推荐一本书《HTTP/2基础教程》,本文不再赘述。

与此同时,Google将HTTP2.0的一些特征抽象出来,形成了QUIC,如果说TCP是专门为Telnet和单独文件传输而被催生的,那么QUIC就是为HTTP2.0而被催生的,舞台和故事的情节依然没变,只是换了演员和道具。HTTP更换了TCP,我个人觉得它会选择QUIC,因为TCP全是问题,而QUIC则全是方案

主角QUIC闪亮登场!

毕竟是HTTP2.0的嫡系,且看QUIC是如何快刀切割TCP在HTTP2.0下所面临的问题的。

多路复用问题

在大的框架下,QUIC从设计之初就是多路复用的,一个QUIC连接可以包含多个虚拟的Stream,虽然在物理上所有的Stream在同一个连接上交错传输,但在逻辑上每一个Stream都有自己的序列号命名空间,其按序性和可靠性独立控制,彻底解决了队头拥塞问题

每一个Stream都有自己的包序列号空间,如果Stream 1的某个包没有按序到达,那么只需要停等该Stream的按序包即可,其它的按序到达的Stream依然可以继续向上层交付。

One thing to keep in mind is HTTP2.0面对的是多路复用问题,并且Stream本身就是HTTP2.0里的概念。

拥塞控制问题

QUIC解决TCP拥塞控制各种问题的手段有两个及其重要的:

  • 完全的时间序发送(导致完全的选择确认)
  • 确认的不可变更

我们知道,TCP发包时是按空间序进行的,即将包的序列号编码到数据序列本身,比如一个文件的字节序列是什么样的,该文件被传输时的包序列号就是什么样的。虽然这种实现方式非常直观,但是这会带来一个问题,即无法应对网络的不稳定性,虽然发送端一厢情愿地希望一个序列按照序列本身的样子被传输,但是网络环境往往并非如你所愿,一旦发送丢包,乱序,发送端就不得不采用激进且莽撞的方式去应对。比如,已经发出了序号10的包,然而序号为5的包超时了,于是重发了序号为5的包,这意味着有一个更小序号的包在更大序号10之后发出了,空间序被时间所打乱,如果此时收到了序号为5的包确认,将无法区分该确认是针对哪个数据包的。这意味着TCP的RTT无论如何都是无法测准的

TCP的拥泵们可能会提点不同的意见,这点我再明白不过了。BBR算法甚至在BBR之前引入的RACK机制不就是时间序发包吗?熟悉代码的朋友可以看看tcp_sock结构体的delivered字段的含义。

但是我要说,一切都是基因决定的!看看TCP的协议头就会知道,即便发送端再渴望采用什么时间序,最终TCP头里带回来的还只是一个积累确认和不多的几个选择确认(而且还要双方均支持且开启!),即便是什么TCP BBR算法也只是一个半吊子BBR算法。

我们来看看QUIC协议,对于发送端每发送一个数据包,不管是新包还是重传包,均为其编码一个新的基于Stream包序列空间的单调递增的序列号,这意味着确认必须完全是选择确认,不可能是积累确认。如果发送端发出了2,3,4,5序列,然后接收端的确认是3,5,那么,发送端会将2,4重新编码为6,7发送之,数据包在Stream本身的按序性则由Stream帧本身的offset字段来保证,与传输行为完全无关!

有了这个时间序特征,发送端便完全可以区分原始包和重传包了,进而得到了更精确的RTT。同时,完全的选择确认可以让发送端获取精确的带宽预估数据,这将使得为拥塞控制而进行的测量将会是准确的,而非猜测的。

是不是非常amazing?!

光说了优点,我们来看看代价。QUIC的完全时间序决定了其完全选择确认,这意味着缺失了积累确认后,接收端的所有确认必须是不可反悔不可抵赖的,即如果你选择确认了某个包,那就不能过一段时间说没有确认这个包。这意味着在乱序度很高的网络上,对端主机将会消耗大量内存来存储ofo(out of order)数据包。嗯,对,就是消耗内存,时间导致的乱序必须由空间来弥补,空间换时间

总而言之,QUIC将“可靠”和“按序”的处理相分离了,分离解耦的双方便可以采用不同的策略了。

也许你会问,为什么TCP当初没有采用这个策略呢?我想在资源匮乏的年代,这种策略是会被骂的吧。再说,停等对于Telnet和文件传输不是挺好的吗?

连接迁移问题

这个话题我并不想多说,我曾经独立设计过OpenVPN在移动端的连接迁移协议,详情看这个:
OpenVPN移动性改造-靠新的session iD而不是IP/Port识别客户端https://blog.csdn.net/dog250/article/details/29180765
当然,QUIC也是类似的机制。这便解决了TCP和应用强耦合的关系,只要TCP断开,应用也要断开,呜呼。

滑动窗口问题

严格来讲,QUIC并不使用严格的基于严格包序列的滑动窗口,而是使用基于最大被确认包序列的窗口,这有一点基于数量而不是序列的窗口的意思。毕竟在内存已经不值钱的时代,offset最大的包都被确认了,就意味着可以继续发更大offset的包,至于之前没收到的,无非就是丢了或者乱序迟到了而已,重发即可。

这种流控策略区分了主次,即大部队先行,留下一小拨人为伤亡者善后即可,解除了单独一个Stream的队头拥塞问题,这个和多路复用一起,便解决了所有的队头拥塞问题。

安全性问题和0RTT问题

和TCP不同,QUIC协议甚至协议包头都是加密且被认证的,和明文的TCP不同,QUIC甚至可以实现0RTT的握手协商,怎么做到的呢?参见:
QUIC协议是如何做到0RTT加密传输的(addons)https://blog.csdn.net/dog250/article/details/80935534

这就是QUIC,一个基于UDP的实现在用户态的非常快的QUIC,如果你看到了QUIC的特性,那么也许现在就是你摒弃TCP的时候了。在我个人看来,TCP一无是处!

曾经,我移植过OpenVPN的Reliable层,它是用于OpenVPN通信TLS握手的一个基于UDP的Layer,在当时我还没有接触QUIC,但已经发现了用UDP实现可靠按序逻辑的灵活性和高效性,为了防止重传叠加,OpenVPN还专门说了一句“千万不要使用TCP”:
Why TCP Over TCP Is A Bad Ideahttp://sites.inka.de/sites/bigred/devel/tcp-tcp.html

嗯,Bad Idea。在我看来,既然QUIC如此优秀,TCP存在的意义除了保护既有的兼容性投资之外,还剩下什么呢?TCP过时了。也许,你并不是必须使用TCP,你使用它也许只是为了保住一份工作,因为毕竟目前而言,大多数的应用依然在使用TCP,所有的人都为了保住工作,于是所有的人都在保着TCP!!说到底,仅仅是为了所谓的The F**king 衣食住行!

QUIC是伟大的,据说日本已经在更高的层面推行quic了:
【重要】通信品質向上にむけた取り組みについてのお知らせhttps://www.ocn.ne.jp/info/announce/2018/06/22_1.html
Aha,相当不错。


后记

我从2006年两年制大专毕业后开始各种求职找工作,各种难,被各种TCP的问题蹂躏,我就职过不多的但也有好几家公司,每一家公司在面试我的时候,都会问超级多的关于TCP的问题(这还不包括面试失败的 NN N N 家公司)。

自2010年3月份起,我对TCP产生了厌恶之感(我受够了)。我讨厌这个协议,并且骨子里恶心。然而我依然在努力学习它,不然单纯的几句抱怨,别人会觉得我是个傻逼。到了后来,我可以自行决定在产品中使用TCP还是UDP的时候,我选择了UDP。每当同事们遇到各种TCP的问题,我都选择回避不想被搅合进去,然而每次当我把TCP的问题解决了之后,我又是何其的兴奋,TCP,不过如此,我一次次保证没有下次了,但身边总是被TCP萦绕,艹!

因为TCP太复杂了,我比较喜欢简单!

但我不惧怕复杂。然而想动TCP,动静太大,场面太大,每次引入一个试验性特性,我便不得不花很久的时间重新编译内核,panic后还要重来,Oh,我在乎时间,这是不允许的,所以我不认为TCP是友好的。既然TCP在IP之上,而UDP继承了IP的语义,我宁愿选择UDP,这就是QUIC,我喜欢QUIC的原因。

Google是一家伟大的公司,感谢持续推出好玩的东西。

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值