PEOPLE DIE IF THEY ARE KILLED! (FACT!)

http 的前世今生

http network

ARPANET

阿帕网

  • 1962年,J·C·R·利克里德创立 IPTO(信息处理处),成为 ARPA(美国高级研究计划局) 的核心机构之一。

  • 1966年,鲍勃·泰勒上任,他在任职期间萌发了新型计算机网络的想法,并筹集资金启动试验。

  • 1967年,拉里·罗伯茨,日后的“阿帕网之父”,在鲍勃·泰勒的一再邀请下,来到 ARPA,着手筹建“分布式网络”。

  • 1968年,罗伯茨提交研究报告《资源共享的计算机网络》,根据这份报告组建的国防部“高级研究计划网”,就是著名的“阿帕网”,于1969年底,阿帕网正式投入运行。

  • 1973年春,日后的互联网之父文顿·瑟夫,和鲍勃·康开始思考如何将阿帕网和另外两个已有的网络相连接(卫星网络和 ALOHA 网),瑟夫设想了新的计算机交流协议,TCP/IP 诞生了。

  • 1975年,ARPA网被转交到美国国防部通信局,此后大量新的网络也开始出现,如 CSNET、BITNET、NSFNET 等。

  • 1982年中期ARPA网被停用,原先的交流协议NCP被禁用,

  • 1983年1月1日,NCP成为历史,TCP/IP开始成为通用协议。

  • 1985年TCP/IP协议进入了 UNIX 系统内核(Sun 公司的微系统工作站)。

  • 1989年被关闭,1990年正式退役。

WWW

1989 年,当时在 CERN (欧洲核子研究中心)工作的 Tim Berners-Lee 博士写了一份关于建立一个通过网络传输超文本系统的报告。这个系统起初被命名为 Mesh,在随后的 1990 年项目实施期间被更名为万维网(World Wide Web)。它在现有的 TCP 和 IP 协议基础之上建立,由四个部分组成:

  • 一个用来表示超文本文档的文本格式,HTML。
  • 一个用来交换超文本文档的简单协议,HTTP。
  • 一个显示(以及编辑)超文本文档的客户端,即网络浏览器。第一个网络浏览器被称为 WorldWideWeb。
  • 一个服务器用于提供可访问的文档,即 httpd 的前身。

这四个部分完成于 1990 年底,且第一批服务器已经在 1991 年初在 CERN 以外的地方运行了。

1991 年 8 月 16 日,Tim Berners-Lee 在公开的超文本新闻组上发表的文章被视为是万维网公共项目的开始。

HTTP/0.9

最初版本的 HTTP 协议并没有版本号,后来它的版本号被定位在 0.9 以区分后来的版本。HTTP/0.9 极其简单:请求由单行指令构成,以唯一可用方法 GET 开头,其后跟目标资源的路径(一旦连接到服务器,协议、服务器、端口号这些都不是必须的)。

GET /mypage.html

响应也极其简单的:只包含响应文档本身。

<html>
  这是一个非常简单的 HTML 页面
</html>

跟后来的版本不同,HTTP/0.9 的响应内容并不包含 HTTP 头。这意味着只有 HTML 文件可以传送,无法传输其他类型的文件。也没有状态码或错误代码。一旦出现问题,一个特殊的包含问题描述信息的 HTML 文件将被发回,供人们查看。

HTTP/1.0

由于 HTTP/0.9 协议的应用十分有限,浏览器和服务器迅速扩展内容使其用途更广:

  • 协议版本信息现在会随着每个请求发送(HTTP/1.0 被追加到了 GET 行)。

  • 状态码会在响应开始时发送,使浏览器能了解请求执行成功或失败,并相应调整行为(如更新或使用本地缓存)。

  • 引入了 HTTP 标头的概念,无论是对于请求还是响应,允许传输元数据,使协议变得非常灵活,更具扩展性。

  • 在新 HTTP 标头的帮助下,具备了传输除纯文本 HTML 文件以外其他类型文档的能力(凭借 Content-Type 标头)。

一个典型的请求看起来就像这样:

GET /mypage.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

200 OK
Date: Tue, 15 Nov 1994 08:12:31 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html
<HTML>
一个包含图片的页面
  <IMG SRC="/myimage.gif">
</HTML>

接下来是第二个连接,请求获取图片(并具有相同的响应):

GET /myimage.gif HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

200 OK
Date: Tue, 15 Nov 1994 08:12:32 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/gif
(这里是图片内容)

在 1991-1995 年,这些新扩展并没有被引入到标准中以促进协助工作,而仅仅作为一种尝试。服务器和浏览器添加这些新扩展功能,但出现了大量的互操作问题。直到 1996 年 11 月,为了解决这些问题,一份新文档(RFC 1945)被发表出来,用以描述如何操作实践这些新扩展功能。文档 RFC 1945 定义了 HTTP/1.0,但它是狭义的,并不是官方标准。

HTTP/1.1

HTTP/1.0 多种不同的实现方式在实际运用中显得有些混乱。自 1995 年开始,即 HTTP/1.0 文档发布的下一年,就开始修订 HTTP 的第一个标准化版本。在 1997 年初,HTTP1.1 标准发布,就在 HTTP/1.0 发布的几个月后。

HTTP/1.1 消除了大量歧义内容并引入了多项改进:

  • 连接可以复用,节省了多次打开 TCP 连接加载网页文档资源的时间。

  • 增加管线化技术,允许在第一个应答被完全发送之前就发送第二个请求,以降低通信延迟。

  • 支持响应分块。

  • 引入额外的缓存控制机制。

  • 引入内容协商机制,包括语言、编码、类型等。并允许客户端和服务器之间约定以最合适的内容进行交换。

  • 凭借 Host 标头,能够使不同域名配置在同一个 IP 地址的服务器上。

一个典型的请求流程,所有请求都通过一个连接实现,看起来就像这样:

GET /zh-CN/docs/Glossary/Simple_header HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/zh-CN/docs/Glossary/Simple_header

200 OK
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Wed, 20 Jul 2016 10:55:30 GMT
Etag: "547fa7e369ef56031dd3bff2ace9fc0832eb251a"
Keep-Alive: timeout=5, max=1000
Last-Modified: Tue, 19 Jul 2016 00:59:33 GMT
Server: Apache
Transfer-Encoding: chunked
Vary: Cookie, Accept-Encoding

(content)


GET /static/img/header-background.png HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/zh-CN/docs/Glossary/Simple_header

200 OK
Age: 9578461
Cache-Control: public, max-age=315360000
Connection: keep-alive
Content-Length: 3077
Content-Type: image/png
Date: Thu, 31 Mar 2016 13:34:46 GMT
Last-Modified: Wed, 21 Oct 2015 18:27:50 GMT
Server: Apache

(image content of 3077 bytes)

HTTP/1.1 在 1997 年 1 月以 RFC 2068 文件发布。

即使 HTTP/1.1 协议进行过两次修订,RFC 2616 发布于 1999 年 6 月,而另外两个文档 RFC 7230-RFC 7235 发布于 2014 年 6 月(在 HTTP/2 发布之前)。HTTP/1.1 协议已经稳定使用超过 15 年了。

HTTP 最大的变化发生在 1994 年底。HTTP 在基本的 TCP/IP 协议栈上发送信息,网景公司(Netscape Communication)在此基础上创建了一个额外的加密传输层:SSL。SSL 1.0 没有在公司以外发布过,但 SSL 2.0 及其后继者 SSL 3.0 允许通过加密来保证服务器和客户端之间交换消息的真实性。SSL 在标准化道路上最终成为了 TLS。

Tim Berners-Lee 对于 Web 的最初设想不是一个只读媒体。他设想一个 Web 是可以远程添加或移动文档,是一种分布式文件系统。大约 1996 年,HTTP 被扩展到允许创作,并且创建了一个名为 WebDAV 的标准。

在 2000 年,一种新的使用 HTTP 的模式被设计出来:REST,不同于 *DAV 扩展,客户端和服务器是可互操作的。RESTful API 在 2010 年变得非常流行。

自 2005 年以来,可用于 Web 页面的 API 大大增加,其中几个 API 为了特定目的扩展了 HTTP 协议,其中就包括 SSE 和 WebSocket:

现状

HTTP 1.1过于庞大

于1996年发布的、描述HTTP 1.0规范的RFC 1945只有60页,但仅仅3年之后、描述HTTP 1.1规范的RFC 2616就一下增长到了176页。而当IETF小组对该规范进行更新时,它更是被拆分成了总页数更多的六个文档(这就是RFC 7230及其文件族的由来与诞生)。总而言之,HTTP 1.1包含了太多细节和可选的部分,这让它变得过于庞大。

过多的可选项

HTTP 1.1不仅包含了非常多的细枝末节,同时也为未来的扩展预留了很多选项。这种事无巨细的风格导致在现有的软件生态中,几乎没有任何实现真正实现了协议中提及的所有细节,甚至要弄清楚“所有细节”到底包括哪些细节都非常困难。而这也导致了很多最初不常用的功能在后来的实现中很少会被支持,而有些最初实现了的功能,却又很少被使用。

随着时间推移,这些当初看似被边缘化的功能逐渐被用上,客户端和服务器的互用性(interoperability)问题就被暴露了出来。HTTP管线化(HTTP pipelining)就是一个非常好的例子。

HTTP 1.1 很难充分利用 TCP 协议所能提供的所有性能

对于同一个域名,浏览器最多只能同时创建 6-8 个 TCP 连接(不同浏览器不一样)。

延迟

HTTP 1.1对网络延迟非常敏感。部分原因是 HTTP pipelining 还存有很多问题,所以对大部分用户来说这项技术是被默认关闭的。

另外,Header 内容多且每次请求时 Header 不会变化太多时,没有相应的压缩传输优化方案也会增加延迟。

线头阻塞(Head-of-line blocking)

每个 TCP 连接一次只能处理一个 请求-响应 ,浏览器按 FIFO 原则处理请求,如果上一个响应还没有返回,则后续 请求-响应 都会受阻,如何解决这个问题?

这个问题可以通过管线化(HTTP pipelining)来优化,浏览器将多个 HTTP 请求整批送出,使得传送过程中不需要先等待服务器的回应。

但是管线化也有问题,如果某个请求响应时间过长,会导致后续请求的阻塞、服务器为了按顺序返回响应,需要缓存多个响应,占用更多资源、浏览器中途断连重试,服务器可能需要重新处理多个请求、客户端,代理,服务器都要支持管线化

优化

Spriting

Spriting是一种将很多较小的图片合并成一张大图,再用JavaScript或者CSS将小图重新“切割”出来的技术。

网站可以利用这一技巧来达到提速的目的——在HTTP 1.1里,下载一张大图比下载100张小图快得多。

但是当某些页面只需要显示其中一两张小图时,这种缓存整张大图的方案就显得过于臃肿。同时,当缓存被清楚的时候的时候,Spriting会导致所有小图片被同时删除,而不能选择保留其中最常用的几个。

内联(Inlining)

Inlining是另外一种防止发送很多小图请求的技巧,它将图片的原始数据嵌入在CSS文件里面的URL里。而这种方案的优缺点跟Spriting很类似。

.icon1 {
    background: url(data:image/png;base64,<data>) no-repeat;
}
.icon2 {
    background: url(data:image/png;base64,<data>) no-repeat;
}

拼接(Concatenation)

大型网站往往会包含大量的JavaScript文件。开发人员可以利用一些前端工具将这些文件合并为一个大的文件,从而让浏览器能只花费一个请求就将其下载完,而不是发无数请求去分别下载那些琐碎的JavaScript文件。但凡事往往有利有弊,如果某页面只需要其中一小部分代码,它也必须下载完整的那份;而文件中一个小小的改动也会造成大量数据的被重新下载。

分片(Sharding)

最初的HTTP 1.1规范提到一个客户端最多只能对同一主机建立两个TCP连接。因此,为了不和规范冲突,一些聪明的网站使用了新的主机名,这样的话,用户就能和网站建立更多的连接,从而降低载入时间。

后来,两个连接的限制被取消了,现在的客户端可以和每个主机建立6-8个连接。但由于连接的上限依然存在,所以网站还是会用这种技术来提升连接的数量。

而随着资源个数的提升,网站会需要更多的连接来保证HTTP协议的效率,从而提升载入速度。

HTTP/2

在 2010 年早期,谷歌通过实践了一个实验性的 SPDY 协议。这种在客户端和服务器端交换数据的替代方案引起了在浏览器和服务器上工作的开发人员的兴趣。明确了响应数量的增加和解决复杂的数据传输。

当HTTPbis小组决定开始制定http2的时候,SPDY已经充分证实了它是一个非常好用的方案。当时已经有人在互联网上成功部署SPDY,并且也有一些文章讨论他的性能。因此,http2便基于SPDY/3草案进行一些修改之后发布了http2 的draft-00。

HTTP/2协议规范(RFC 7540)于2015年5月发表,在那之后,该协议已在互联网和万维网上得到广泛的实现和部署。

http2 的设计原则与兼容性考虑

http2 到底做了些什么呢?事实上,http2 有着非常严格的边界

  • http2必须维持HTTP的范式。毕竟它只是一个让客户端发送请求到服务器的基于TCP的协议。

  • 不能改变 http://https:// 这样的URL,也不能对其添加新的结构。使用这类URL的网站太多了,没法指望他们全部改变。

  • HTTP1的服务器和客户端依然会存在很久,所以必须提供HTTP1到http2服务器的代理,也要让这种代理能够将http2的功能一对一的映射到HTTP 1.1的客户端。

  • 删除或者减少协议里面那些可选的部分,让协议里所有的内容都成为了强制性要求。

  • 不再使用小版本号。服务器和客户端都必须确定自己是否完整兼容http2或者彻底不兼容。如果将来该协议需要被扩充或者变更,那么新的协议将会是http3,而不是http 2.x。

http2 和现有的URI结构

现有的URI结构正在被HTTP 1.x使用而不能被更换,所以http2也必须沿用该结构。因此不得不找到一种方式将使用的协议升级至http2。HTTP 1.1本身就制定过“升级”的方案:提供一个首部字段,表示允许服务器在收到旧协议请 求的同时,可以向客户端发送新协议的响应。但这一方案往往需要花费一次额外的往返通信来作为升级的代价。(WebSocket就是这种方式)。

而这一代价是SPDY团队不想接受的。因为他们只实现了基于TLS的SPDY,所以他们开发了一个TLS的扩展去简化协议的协商。这个扩展被称作NPN(Next Protocol Negotiation),借助于此,服务器会通知客户端所有它支持的协议,让客户端从中选择一个合适的来进行通讯。

https:// 所准备的http2

SPDY依赖于TLS,所以按理说TLS也应成为http2 必需的组件,不过出乎大家意料的是http2将TLS标记成了可选。然而,Firefox和Chrome都明确地表示,他们只会实现基于TLS的http2。

选择TLS的原因的其中之一是希望保护以及尊重用户的隐私,另外,人们普遍认为任何来自80端口的流量都是基于HTTP 1.1亦或者是其某个变种的,而不是另外一种全新的协议,更利于区分 http2。

http:// 所准备的http2

正如我们之前所提到的,对于纯文本的HTTP1.1来说,协商http2的方法就是通过给服务器发送一个带升级头部的报文。如果服务器支持http2,它将以“101 Switching”作为回复的状态码,并从此开始在该连接上使用http2。也许 你很容易就发现这样一个升级的流程会需要消耗掉一整个的往返时延,但好处是http2连接相比HTTP1可以被更大限度地重用和保持。

直到今天,没有任何主流浏览器支持非TLS的http2。

基于TLS之上的http2协商

Next Protocol Negotiation (NPN)是一个用来在TLS服务器上协商SPDY的协议。IETF将这个非正式标准进行规范化,从而演变成了ALPN(Application Layer Protocol Negotiation)。ALPN会随着http2的应用被推广,而SPDY的客户端与服务器则会继续使用NPN。

由于NPN先于ALPN诞生,而ALPN又经历了一些标准化过程,所以许多早期的http2客户端和服务器在协商http2时会将这两者同时实现。

ALPN和NPN的主要区别在于:谁来决定通信协议。在ALPN的描述中,是让客户端先发送一个协议优先级列表给服务器,由服务器最终选择一个合适的。而NPN则正好相反,客户端有着最终的决定权。

协议

二进制分帧层(Binary Framing Layer)

帧是数据传输的最小单位,以二进制传输代替原本的纯文本传输,原本的报文消息被划分为更小的数据帧。

多路复用(MultiPlexing)

在一个连接上,可以向对方不断发送帧,每帧的 stream identifier 标明这一帧属于哪个流,然后在 接收时,根据 stream identifier 拼接每个流的所有帧组成一整块数据。

每个请求当作一个流,多个请求就有多个流,每个 请求-响应 数据分成多个帧,不同流中的帧交错的发送给 对方,这就是 HTTP2 的多路复用。

这样就实现了单连接上多 请求-响应 并行,解决了线头阻塞问题,也减少了 TCP 连接数量和 TCP 连接慢启 动造成的问题(http2 对于同一域名可以只创建一个连接)。

服务端推送(Server Push)

这个功能通常被称作“缓存推送”。主要的思想是:当一个客户端请求资源X,而服务器知道它很可能也需要资源Z的情况下,服务器可以在客户端发送请求前,主动将资源Z推送给客户端。这个功能帮助客户端将Z放进缓存以备将来之需。

服务器推送需要客户端显式的允许服务器提供该功能。但即使如此,客户端依然能自主选择是否需要中断该推送的流。如果不需要的话,客户端可以通过发送一个RST_STREAM帧来中止。

Header 压缩,HPACK

HTTP是一种无状态的协议。简而言之,这意味着每个请求必须要携带服务器需要的所有细节,而不是让服务器保存住之前请求的元数据。因为http2并没有改变这个范式,所以它也以同样原理工作。

当一个客户端从同一服务器请求了大量资源(例如页面的图片)的时候,所有这些请求看起来几乎都是一致的,而这些大量一致的东西则正好值得被压缩。

HTTPS和SPDY的压缩机制被发现有受BREACH和CRIME攻击的隐患。通过向流中注入一些已知的文本来观察输出的变化,攻击者可以从加密的载荷中推导出原始发送的数据。

HTTPbis小组尝试设计新的压制机制,HPACK,HTTP/2头部压缩,顾名思义它是一个专为http2头部设计的压缩格式。确切的讲,它甚至被制定写入在另外一个单独的草案里。新的格式同时引入了一些其他对策让破解压缩变得困难, 例如采用帧的可选填充和用一个bit作为标记,来让中间人不压缩指定的头部。

应用层的重置连接

HTTP 1.1的有一个缺点是:当一个含有确切值的Content-Length的HTTP消息被送出之后,你就很难中断它了,通常可以通过设置 tcp segment 里的 reset flag 来通知对端关闭连接的。这种方式会直接断开连接,下次再发请求 就必须重新建立连接。HTTP/2 引入 RST STREAM 类型的 frame,可以在不断开连接的前提下取消某个 request 的 stream。

请求优先级设置

每个流都包含一个优先级(也就是“权重”),它被用来告诉对端哪个流更重要。当资源有限的时候,服务器会根据优先级来选择应该先发送哪些流,解决了关键请求被阻塞的问题。

借助于PRIORITY帧,客户端同样可以告知服务器当前的流依赖于其他哪个流。该功能让客户端能建立一个优先级“树”,所有“子流”会依赖于“父流”的传输完成情况。

优先级和依赖关系可以在传输过程中被动态的改变。

流量控制

每个 http2 流都拥有自己的公示的流量窗口,它可以限制另一端发送数据。对于每个流来说,两端都必须告诉对方自己还有足够的空间来处理新的数据,而在该窗口被扩大前,另一端只被允许发送这么多数据。

QUIC

Google的QUIC (快速UDP互联网连接)协议是一个非常有趣的试验,它在很大程度上继承了SPDY的衣钵。QUIC是一个UDP版的TCP + TLS + HTTP/2替代实现。

最初的QUIC协议由Jim Roskind在Google设计并于2012年实现,经过Google的扩大试验后,于2013年向全世界公开发布。

Google实现了QUIC协议,并随后将它部署在Chrome、Gmail、YouTube等中。他们相当迅速地迭代该协议的版本,经过一段时间,协议的理念被许多用户的使用证明可以面向大量用户可靠运作。

2015年6月,首个QUIC的互联网草案被提交到IETF以进行标准化,但直到2016年下半年,一个QUIC工作组才被批准成立并投入工作。随后它在各方的高度关注下迅速发展。

(HTTP/2规范RFC 7540发布于2015年5月,只比QUIC首次进入IETF早一个月。)

在2017年,Google的QUIC工程师称,整个互联网中大约有7%的流量在使用该协议(Google版本)。

为什么使用 QUIC?

TCP队头阻塞

TCP队头阻塞(head of line blocking)

HTTP/2是基于TCP实现的。相比之前的版本,HTTP/2使用的TCP连接数少了很多。采用HTTP/2时,浏览器一般会在单个TCP连接中创建并行的几十个乃至上百个传输。

如下图所示,我们用一个链条来表现一个连接上发送的两个流(传输),红色的与绿色的数据流:

如果HTTP/2连接双方的网络中有一个数据包丢失,或者任何一方的网络出现中断,整个TCP连接就会暂停,丢失的数据包需要被重新传输。因为TCP是一个按序传输的链条,因此如果其中一个点丢失了,链路上之后的内容就都需要等待。

这种单个数据包造成的阻塞,就是TCP上的队头阻塞(head of line blocking)。

随着丢包率的增加,HTTP/2的表现越来越差。在2%的丢包率(一个很差的网络质量)中,测试结果表明HTTP/1用户的性能更好,因为HTTP/1一般有六个TCP连接,哪怕其中一个连接阻塞了,其他没有丢包的连接仍然可以继续传输。

在限定的条件下,在TCP下解决这个问题相当困难。

独立的数据流避免阻塞问题

使用QUIC时,两端间仍然建立一个连接,该连接也经过协商使得数据得到安全且可靠的传输。

但是,当我们在这个连接上建立两个不同的数据流时,它们互相独立。也就是说,如果一个数据流丢包了,只有那个数据流必须停下来,等待重传。

下面是两个端点间的示意图,黄色与蓝色是两个独立的数据流。

用TCP还是UDP

如果我们无法解决TCP内的队头阻塞问题,那么按道理,我们应该在网络栈中发明一个UDP和TCP之外的新型传输层协议。但是互联网上的很多设备只认TCP和UDP。如果使用另一种传输层协议,那么就会有N%的连接无法建立,这些 中间设备会认为除TCP和UDP协议以外的协议都是不安全或者有问题的。

另外,网络栈中的传输层协议改动一般意味着操作系统内核也要做出修改。更新和部署新款操作系统内核的过程十分缓慢,需要付出很大的努力。由IETF标准化的许多TCP新特性都因缺乏广泛支持而没有得到广泛的部署或使用。

协议僵化

为了保证互联网工作正常,我们需要在互联网各处搭建各种设备,在网络之间转发(路由)数据包、阻挡恶意流量、执行地址转换(NAT)、提升性能、监视流量等等。这些设备更新很慢,经常跟不上最新的技术。

在这些设备部署之后的一段时间里,协议有了新的特征。而在这些设备引入(了解)这些新特性之前,它们会认为这种特征的数据包是非法的、恶意的,于是会将这种流量直接扔掉。

这种问题就被称之为“协议僵化”。协议僵化也影响了TCP协议的改变,当客户端与远程服务器之间的某些中间设备检测到对于它们来说未知的新的TCP选项时,中间设备将拦截这些流量,因为它们不知道这些选项的作用。

尽可能将通信加密是对抗僵化的唯一有效手段,加密可以防止中间设备看到协议传输的绝大部分内容。

安全性

QUIC始终保证安全性。QUIC协议没有明文的版本,所以想要建立一个QUIC连接,就必须通过TLS 1.3来安全地建立一个加密连接。如上文所说,加密可以避免协议僵化等拦截和特殊处理。这也使QUIC具有了Web用户所期望的所有的HTTPS安全特性。

QUIC只在加密协议协商时会发送几个明文传送的初始握手报文。

减少延迟

与TCP的3次握手相比,QUIC提供了0-RTT和1-RTT的握手,这减少了协商和建立新连接时所需的时间。

(RTT:往返时延)

除此之外,QUIC提供了提早传输更多数据的“早期数据”(early data)特性,并且它的使用比TCP快速打开(TCP Fast Open)更加简便。

因为数据流概念的引入,客户端不用等待前一个连接结束,便可以与同一个主机建立另一个逻辑连接。

协议特点

可靠性

虽然UDP不提供可靠的传输,但QUIC在基于UDP之时增加了一层带来可靠性的层。它提供了数据包重传、拥塞控制、调整传输节奏(pacing)以及其他一些TCP中存在的特性。

只要连接没有中断,从QUIC一端传输的数据迟早会出现在另一端。

数据流

QUIC在同一物理连接上可以有多个独立的逻辑数据流。这些数据流并行在同一个连接上传输,不影响其他流。

连接在两个端点之间经过类似TCP连接的方式协商建立。QUIC连接基于UDP端口和IP地址建立,而一旦建立,连接通过其“连接ID”(connection ID)关联。

在已建立的连接上,双方均可以建立传输给对方的数据流。单一数据流的传输是可靠、有序的,但不同的数据流间可能无序传送。

QUIC可对连接和数据流分别进行流量控制(flow control)。

有序交付

QUIC的单个数据流可以保证有序交付,但多个数据流之间可能乱序。这意味着单个数据流的传输是按序的,但是多个数据流中接收方收到的顺序可能与发送方的发送顺序不同!

举个例子:服务器传送流A和B到客户端。流A先启动,然后是流B。在QUIC中,丢包只会影响该包所处的流。如果流A发生了一次丢包,而流B没有,流B将继续传输直到结束,而流A将会进行丢包重传过程。而在HTTP/2中这不可能发生。

下图展示了连通两个QUIC端点的单一连接中的黄色与蓝色的数据流。它们互相独立,所以可能乱序到达,但是每个流内的信息将按序可靠到达。

快速握手

QUIC提供0-RTT和1-RTT的连接建立,这意味着QUIC在最佳情况下不需要任何的额外往返时间便可建立新连接。其中更快的0-RTT仅在两个主机之间建立过连接且缓存了该连接的secret时可以使用。

QUIC允许客户端在0-RTT的情况下直接捎带数据。这使得客户端能尽早向对方传送数据,当然也使得服务器能更快地发回数据响应。

TLS 1.3

QUIC使用TLS 1.3传输层安全协议(RFC 8446)。QUIC没有非加密的版本。

与更早的TLS版本相比,TLS 1.3有着很多优点,但使用它的最主要原因是其握手所花费的往返次数更低,从而能降低协议的延迟。

Google的传统QUIC使用一个自行定制的加密法。

QUIC工作原理

连接

  • 连接ID(Connection ID)

    每个连接过程都有一组连接标识符,或称连接ID,该ID用以识别该连接。每个端点各自选择连接ID。每个端点选择对方使用的连接ID。

    连接ID的基本功能是确保底层协议(UDP、IP及其底层协议)的寻址变更不会使QUIC连接传输数据到错误的端点。

    利用连接ID的优势,连接可以在IP地址和网络接口迁移的情况下得到保持——而这TCP永远做不到。举例来说,当用户的设备连接到一个Wi-Fi网络时,将进行中的下载从蜂窝网络连接转移到更快速的Wi-Fi连接。与此类似,当Wi-Fi连接不再可用时,将连接转移到蜂窝网络连接。

使用TLS的连接

在初始的数据包建立连接之后,连接发起者会马上发一个加密的帧以开始安全层握手。安全层使用TLS 1.3协议。

在QUIC中,没有方法或机制避免使用TLS连接。该设计旨在使中间设备难以篡改数据包,防止协议僵化。

0-RTT

先前已连接过一个服务器的客户端可能缓存来自该连接的某些参数,并在之后与该服务器建立一个无需等待握手完成就可以立即传输信息的0-RTT连接,从而减少建立新连接所必需的时间。

HTTP/3

<//> URL

HTTP/3 将使用 <//,如同> HTTP/2 一样,HTTP/3 不会引入新的URL方案。

使用 Alt-svc 自举

一个HTTP服务器的响应中包含了如下的一个 Alt-Svc: 头部:

Alt-Svc: h3=":50781"

这指示了同一名称的主机在UDP端口50781提供HTTP/3服务。

然后,客户端可以尝试与该端口建立QUIC连接。如果成功,后续将通过该连接继续通信,代替初始的HTTP版本。

与HTTP/2的比较

HTTP/3面向QUIC设计,QUIC是一个自己处理数据流的传输层协议。

HTTP/2面向TCP设计,因此数据流在HTTP层处理。

相似之处

这两个协议为客户端提供了几乎相同的功能集。

  • 两者都提供数据流

  • 两者都提供服务器推送

  • 两者都有头部压缩,QPACK与HPACK的设计非常类似

  • 两者都通过单一连接上的数据流提供复用

  • 两者都提供数据流的优先度设置

不同之处

两个协议的主要不同点在于细节,不同之处主要由HTTP/3使用的QUIC带来。

  • 得益于QUIC的0-RTT握手,HTTP/3可以提供更好的早期数据支持,而TCP快速打开和TLS通常只能传输更少的数据,且经常存在问题。

  • 得益于QUIC,HTTP/3的握手速度比TCP+TLS快得多。

  • HTTP/3不存在明文的不安全版本。尽管在互联网上很少见,HTTP/2还是可以不配合HTTPS来实现和使用。

  • 通过ALPN(应用层协议协商)拓展,HTTP/2可以直接在TLS握手时进行协商。HTTP/3基于QUIC,所以需要凭借响应中的 Alt-Svc: 头部来向客户端宣告。