HTTP篇-HTTP0.9 到 HTTP3

168 阅读8分钟

​最近在学习极客时间的《浏览器工作原理与实践》,里面提到了 HTTP 的发展,总结一下:

HTTP 0.9

最早诞生的 HTTP0.9 主要用于学术交流,需求很简单,只需要传输仅有文本内容的 HTML 文件即可,这里实现也非常简单,只要客户端发起请求,服务器响应即可。

一个基于 HTTP0.9 的请求过程:

  1. 先是 DNS 解析获取服务器 IP 地址

  2. 然后与服务器 TCP 三次握手建立连接

  3. 建立连接后,发送一个 GET/index.html 的请求信息获取 index.html 文件

  4. 服务器收到请求后,以 ASCII 字节流的方式返回到客户端

  5. 客户端收到 index.html 内容后,断开连接

所以总的来说,HTTP0.9 的请求,并不需要请求头和响应头,仅仅发送 GET/index.html 的方式,就可以告诉服务器需求是什么,服务器也不需要告诉客户端其他内容,仅仅把 index.html 以 ASCII 码的方式返回即可。

HTTP 1.0

但随着浏览器的发展,万维网不仅仅用于学术交流了,因此多文件的支持的需求应时而生。

HTTP 1.0 在 0.9 的基础上,支持了多文件的下载,像 CSS、JS、视频文件等,那客户端是如何支持这么多种类的文件的下载呢?客户端需要告诉服务器要什么编码以及类型的文件,服务器也需要告诉客户端关于返回内容的信息,于是 HTTP 1.0 引入了请求头与响应头。

当客户端发送请求时,请求上带上请求头告知服务器请求的信息,而服务器返回内容时会带上返回内容相关信息的响应头。

一个基于 HTTP1.0 的请求:

  1. 先是 DNS 解析获取服务器 IP 地址

  2. 然后与服务器 TCP 三次握手建立连接

  3. 建立连接后,客户端在请求时请求头带上,

  4. 服务器收到请求后,返回内容时将响应头带上返回到客户端

  5. 客户端收到内容后,断开连接

另外,为了减少传输的文件大小,HTTP 1.0 还可能会在请求头上带上希望服务器返回的压缩文件的压缩方式,但有可能服务器无法响应该请求,或者请求处理失败,服务器需要告知客户端处理结果,于是 HTTP1.0 还多了状态码的响应行;为了减少服务器压力,HTTP1.0 还提供了 Cache 模式,告知客户端可以缓存文件,下次请求时可以直接使用缓存文件。除此以外,HTTP 1.0 还提供了用户代理字段供服务器统计访问的客户端是 macOS 与 Windows的用户数量。

HTTP1.1

随着浏览器的普及,单个页面里面的请求越来越多,有时候一个文件里面的请求上百个的时候,我们的每个请求都需要经历建立连接再断开连接的过程,无疑会增加了很多开销。

于是 HTTP1.1 增加了持久连接功能,也就是,在一个 TCP 连接里面,可以发送多个HTTP 请求,只要客户端和服务器没有明确的断开,都会一直保持持久连接状态,这样就减少了多次的 TCP 建立以及断开连接的开销,减少服务器额外的负担并且减少了HTTP 的请求时长,这个持久连接在 HTTP1.1 中是默认开启的,如果不想开启,可以在请求头中加入 Connection: close 来关闭。目前浏览器对于同一个域名,默认可以同时开启 6 个 TCP 持久连接。

但这里也存在个问题:对头阻塞,一旦某一 TCP 连接通道的请求返回时间过长,都会阻塞其他请求的响应,针对这个问题 HTTP1.1 尝试用管道化(批量发送请求到服务器)的方式来解决,但即使可以批量发送请求到服务器,服务器依旧需要根据请求顺序来响应,这个管道化的方法最终都大部分的浏览器放弃了。

而在 HTTP1.1 中,还提供了 host 的请求头,区分多域名对一个 IP 地址时当前的域名地址是什么。在 HTTP1.0 中,响应头会返回 Content-length 的值告知浏览器当前返回的文件的大小以便浏览器接收文件,而随着服务端技术发展,很多返回的内容都是动态生成的,针对这些动态内容的支持,HTTP1.1 处理时,通过将数据分成多个数据块,前一个数据块的大小会在后一个数据块的返回中附上,最后以一个零长度的数据块结束数据的发送。另外针对安全方面, HTTP1.1 还提供了 cookie 的支持。

HTTP 2

虽然 HTTP1.1 使用了很多优化资源加载速度的手段,也有一定的效果,但 HTTP1.1 还是存在着一个痛点:对带宽的利用率不高(带宽是指每秒最大能发送或者接收的字节数。我们把每秒能发送的最大字节数称为上行带宽,每秒能够接收的最大字节数称为下行带宽。)

原因有以下几点:

  1. TCP 的慢启动

    慢启动你可以类比为一辆车要起步出发的场景,起先会以一个很慢的速度传输数据,慢慢再达到一个理想状态,但这个慢启动是 TCP 为了避免网络拥挤的做法,我们没法改变。之所以说慢启动会影响性能,是因为页面上本来一些关键的文件像 css、js 文件本来文件大小并不大,这些文件应该要等 tcp 连接建立后就马上响应的,但由于 tcp 是慢启动的传输过程,并不能很快响应所以就推迟了首次渲染的时长了。

  2. 多 TCP 连接时,会竞争固定带宽,不能协商哪些优先处理

    当系统建立了多条 TCP连接时,当带宽充足时,tcp 的传输速度很快,但一旦带宽不足时,这些 TCP 连接的传输速率也会顺应减慢。这样就会出现一个问题。某些TCP 通道下载的是重要的文件,某些 TCP 通道下载的是不重要的后续才需要的文件时,这些不重要的文件这个时候会来竞争这宝贵的带宽,但又不能协商哪个优先处理,从而阻塞了页面的交互。

  3. 队头阻塞

    队头阻塞也就是上方 HTTP 1.1 所说的,这里就不再多提了。

尽管 TCP 的慢启动与带宽竞争我们还无法解决,那我们可以规避这些问题。于是 HTTP2 就使用了一个域名只建立一个 TCP 连接,那么这个时候就避免了慢启动的问题,同时也避开了多 TCP 连接的带宽竞争问题。而针对对头阻塞,对头阻塞也就是,一个请求要等另一个请求完成后才可以发起请求,所以 HTTP2 需要实现并行请求的方式,也就是,任何时候都可以像服务器发起请求,不需要等待,服务器也可以随时响应请求。所以 HTTP2 做的也就是一个域名只建立一个 TCP 连接以及消除对头阻塞,这个也就是 HTTP2 多路复用技术解决的问题。

HTTP2 添加了个二进制分帧层来实现多路复用,会将请求分帧处理发送到服务器,同一个请求帧上都带有相同的 id,不同请求帧的id 不同,当服务器接收完所有的帧后会将id 相同的帧合并为一条完整的请求处理返回到浏览器的二进制分帧层,同样,二进制分帧层也会对响应数据做分帧处理再响应给用户。并且 HTTP2 提供了优先级的设置,在当服务器收到优先级高的请求时,会暂停之前的请求的处理,优先返回优先级高的请求内容,而且,HTTP2 还允许设置提前告知服务器后续需要请求的内容,这样子,在请求 HTML 的时候,服务器也可以知道后续要请求的 CSS 、js 文件的需求以便一起发送给客户端,这一步对首屏渲染的速度起了很重要的作用。除此以外,HTTP2 还对请求头以及响应头都进行了压缩,一定程度提升了传输速率。

HTTP3

HTTP2 虽然解决了队头阻塞问题并且一定程度上规避了 TCP 慢启动以及 TCP 带宽竞争问题,但并不是最佳的解决方法,在 HTTP3 中,直接甩掉了 TCP 的包袱,在 UDP 的基础上实现类似 TCP 多路数据流、传输可靠性等功能,也就是 QUIC 协议。为什么要选 UDP 呢?一个原因是:TCP 的连接的建立是需三次握手,而 UDP 连接的建立则是两次;另一个原因则是 TCP 的慢启动。但目前 QUIC 协议还有很长一段路要走,chrome 虽然早前支持 QUIC 协议但与官方相比还是有很大的区别,另外系统内核对 UDP 的优化远远没有达到 TCP 的优化程度,中间设备对 UDP 的优化度也还不如 TCP,丢包率还是存在不小。

打个广告,公众号搜索:拯救世界大超人,恶补知识的超人会时不时更新学习或工作内容)