HTTP 的前世今生
插曲知识
让信息在设备间高效可靠的传递
- 电路交互:吞吐率低
- 报文交换:分而制之
- 分组交换:多路复用
HTTP 诞生背景
在计算机还未广泛普及的 1989 年,使用计算机的大多是各国实验室与研究院的学究。作为学者与研究员,他们在日常工作生活中使用最大的就是各种文件资料,而且大家在工作与学习时,分享彼此的心得与成果不可避免就要相互交换文件,如果都在一个单位还好,可以直接跑过对方的工位上给对方,但如果彼此是在不同的城市,那就比较麻烦了,当时只能通过邮寄。但这样一来一去就耽误了大量的时间,而且如果要给多个人分享知识,那就要复印多份,这实在是太浪费和低效了。
在这样的背景下,Tim BernersLee(当时欧洲核子研究组织的一个博士) 提出了一种基于计算机能让身处不同城市甚至国家的研究者共享文件知识的一种设想。
- 在计算机中统一用超文本标记语言 HTML(HyperText Markup Language)书写文件;
- 用 HTML 书写的文件要在网上放一个固定的地方,也就 URL(Uniform 12 Resource Locator),统一资源定位符;
- 还需要一个相当于邮递员的角色,来把上面放在一个固定位置的文件送到要阅读者的手中,这就是 http 的诞生。
- 还需要写一个应用程序,来为阅读者解析呈现拿到的用 HTML 书写的文件,这就是我们现在浏览器了。
1990 年 12 月 20 日诞生了这个世界上第一个上线的网站,下面我们来欣赏一下最早的网站:
HTTP/0.9
这个版本主要就是 Tim BernersLee 一个人捣鼓出来,那时他也就想着在单位内部研究员们一起用用,方便大家共享文件与成果也就行了,所以现在看来比较简陋
- 只有 get 一种方法
- 只响应 html 本身
但不得不说他真他妈是个人才。
HTTP/1.0
Tim 的第一个网站上线后,大家都很喜欢这种共享的文件与知识传播方式,特别是研究工作者。慢慢的世界各地的许多机构都开始了用这种方式来管理文件。1993 年 CERN 向公众开放使用万维网,咋也没想到这就一发而不可收拾。用的人多了,那么问题一些以前可以凑合的东西,慢慢也就不行了,其实 http/0.9 当时还不是一个统一的标准,大家时不时会搞一些自己的想法进去,这很有趣,但是这对推广和大范围使用就产生了阻碍,于是世上的有识之士就聚在一起搞了个标准出来,希望大家都按这标准走。于是 http/1.0 就诞生了。
- 增加可以传输文件的类型,如图片,视频
- 状态码
为啥有了 1.0 之后还有继续跟新呢?
当然是因为时代在进步啦,随着其它技术的进步和网站内容的丰富,1.0 有四个痛点问题。
- 短连接:在使用 http1.0 的过程中人们发现,每次发送请求都要重新建立 tcp 连接,而 tcp 连接的建立都是要进行三次握手的,人们给这种方式取了个名字,“短连接”。这样在当时网页内容已经较多的背景下,传输耗时就是一个需要解决的问题。
- host 头域:服务器变得更强了,它一个人可以干好几个人的活儿,于是有了虚拟主机技术。有了支持部署多个网站的硬实力。但是这些虚拟主机都还是在一个物理主机上,那么他们的 ip 就是同一个,无奈 1.0 区分不了啊
- 断点续传:以前文件都先对比较小,用 1.0 一次就把整个文件都传了,也没啥问题,但是后面文件越来越大了,1 次传,很容易出现传了一半因为其它原因中断了。再次传输又要把前面传了一半的文件再传一遍,这谁受的了,也太浪费了同时耗时也更久。
- 缓存控制不够用:1.0 的缓存方式只有两种,分别是 header 中的 if-modified-since 和 expires。但是后面开发者需要用匹配缓存或者打 tag 等场景来缓存。
HTTP/1.1
首先需要解决的事 1.0 出现的 4 个问题,解决方式如下:
- Keep-alive: 既然短连接是因为,频繁连接而导致的,那么我们就来让这个 tcp 链接保持连接状态不就好了吗?是的这就是解决方案,于是便有了 keep-alive。但是我们放弃了原来的一次传输建立一次 tcp 连接的方式,只用一个 tcp 连接,那么问题又来了,我们如何区分我们传输的文件避免混乱呢。最简单容易的就是所有要传输的文件都来排个对,我们用这个 tcp 来把它们一个个送到目的地,这也就是 pipeline 呢。
- host 头域:增加这个头域后,我们就可以用它来在一台有多个虚拟主机的物理主机上,用这个域名头来区分虚拟主机的 web 服务器了。
- range 头域:新增了这个头域,允许只请求资源的部分,这样我们的断点续传就能得到解决了。同时也为它新增了一个状态码,206。
- 引入更多的缓存策略,如 Entity tag, if-unmodified-since,if-match,if-none-match 等等。
1.1 在解决上面问题的同时也进一步规范了 http 的语义。
为啥有了 1.1 之后还有继续跟新呢?
当然是因为时代在进步啦,不知细心的你是否发现,keep-alive 用一个 tcp 传输文件是用的排队传输方式进行的。
好比小刘去银行贷款办理业务(经济下行日子不好过哟),他就需要排队等业务员先处理前面人的事情,眼看只有俩个人就到自己了,不巧李大爷,正好在办理,大爷填了半天表给业务员后,业务员一看这填的不对啊,大爷你再重新填一张。于是后面的人又要等一会了,也包括小刘。1.1 诞生时,那时要办理业务的人就那么几个,大爷再多填几次也都还能接受,但是后面排对需要办理业务的人过百时,那问题就有点严重了,如果中间再多几个王大爷,张大妈那就更难了。用 tcp 传输文件也是如此,如果排队传输的文件经常丢包,经常需要重传,那后面等待传输的文件就要等老鼻子久了,这就是 http 层队头阻塞。
HTTP/2.0
首先主要还是要解决 1.1 的队头阻塞,解决方式如下:
首先上面我们已经知道了,队头阻塞的原因。既然是因为前面排队的文件需要重传而阻塞了整个队列其它文件的传输,那么我们能不能多个文件一起来传输呢?本来我们的文件在传输时就会分成很多的 chunks,现在我们把多个文件的 chunks,混合一起发送,这样就算其中有一两个文件丢包了需要重传那其它的文件还是可以继续传输。
如果这样做的话,就收端收到的就会是多个文件混合后的 chunks,我们还有想个办法把这些混合的 chunks 区分开来,再进行组合得到一份完整的文件。那如何把混合在一起的 chunks 区分呢?
你是不已经想到了,那就是在把文件生成成 chunks 时,给每份 chunk 都打个 tag,这个 tag 又是和文件相关联,实际操作时是给了同一个文件生成的 chunks 一个相同的 ID,接受端按 ID 来区分 chunks 并归类,组合成完整的文件。这就是 2.0 的多路复用。
当然从 1997 年到 2015 这 10 多年的时间,不可能只新增了一个多路复用啥,2.0 还做了些其它的优化:
-
解析协议由原来的文本协议升级为二进制协议,这样就避免了文本的多样性而产生的健壮性问题;
-
由于多次的升级 header 头中的信息也越来越多了,对其进行压缩,cache 也能减少传输的体积;
-
支持服务端推送,比较要用到服务端通知的场景越来越多了,一直依赖 websockt 等搞法也不合适;
-
相信大家,在用 2.0 之前一定用过 https,为了解决安全传输问题加密可少不了,所以 2.0 也要用 TLS 来加密。
2.0 的多路复用机制解决了 http 层的队头阻塞问题,当是由于底层的 TCP 这次可能一次是传输了多个文件的乱序数据包,并不能直接给应用层使用,还需要再整合,如果在传输时其中某个文件的一个包丢失了,就必须等待重传,这时其它的文件的数据包传输也就被阻塞了,不能整合成完整的文件给应用层使用。这就是 tcp 层面的队头阻塞。
HTTP/3.0
3.0,要解决 2.0 的 tcp 的队头阻塞,最容易想到的就是重写 tcp,但这中内核层面的修改就动了根本了。现在的许多网络基础设施都需要配合这升级,这推广起来无疑非常难以实现。重写 tcp,至少现在来说还不太现实(未来可能会重新从底层优化的其它协议),那问题咋解决呢,谷歌的工程师们想到了 TCP 异姓兄弟 UDP,2012 年时基于 UDP 的新协议 QUIK 诞生了。
一说到 UDP,你是不第一次想到的就是不稳定,不可靠。那么如果要想基于 UDP 实现 http2.0 的升级,该如何解决可靠性和稳定性呢?
为了保证可靠性,加密必不可少,所以我们的客户端和服务端要进行秘钥交换,QUIC 使用的是 HD 交换算法。
为了保证稳定性,校验也一定要有,同时还要实时纠错,QUIC 使用的向前纠错技术简称 FEC,有点类似于用空间换时间的概念,QUIC 每次发送数据时都要对数据进行异或运算,并将运算的结果和数据一起发送出去,接受方收到数据后把数据和发过来的运算结果进行对比,如果数据有缺失则立即向发送方反馈,要求再发。
http3.0 是在 QUIC 的基础上演进出来的,它继承了 QUIC 的全部优点,同时也做了一些其它优化,如:
- 头部加密由 HPACK 优化为 QPACK
目前的缺陷:
很多网络设备以前为了更好支持 TCP,对 UDP 数据包做了些不友好的限制,因为拦截而导致链接成功率下降。
总结
由上面我们可以看出,http 的从无到有,由弱到强就是一个不断迭代的过程,每次都着重解决当下最迫切的需求,随不完美但够用就好,然后随着时代的发展,自己再每次进步一点点。
想想我们的生活,学习,工作难道不也是应该如此吗?或许你当下不能做到最好,但只要不断调整,不断迭代,你终将会......
个人博客:https://liuguang2016.github.io