HTTP/1.1
仅仅在HTTP/1.0公布后的几个月,HTTP/1.1发布了,是目前主流HTTP协议的版本,也是目前为止使用最为广泛、延用时间最为久远的HTTP版本,以至于随后的近10年时间里都没有新的HTTP协议版本发布。对比之前的版本,其主要更新如下:
- 默认长连接机制
- Pipeline机制
- header中引入host
- Chunked编码传输
- 更全面的Cache机制
- 引入OPTIONS, PUT, DELETE, TRACE和CONNECT方法
本文主要介绍了HTTP协议的演进过程,从HTTP/0.9到目前HTTP/2中各个版本的特点以及成因。通过对比各个版本的特点以及相关数据的支持来讲解整个HTTP协议的演进过程。此外,文中还会涉及一些相关协议概念,包括TCP/IP、DNS、HTTPS、QUIC、SPDY等,正是这些协议与HTTP一起为我们展现了一个丰富多彩的互联网的世界。
HTTP(HyperText Transfer Protocol)是万维网(World Wide Web)的基础协议,它制定了浏览器与服务器之间的通讯规则,它由Berners-Lee和他的团队在1989-1991年期间开发完成,至今共经历了3个版本的演化。
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,用于万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
在OSI七层模型中,HTTP协议位于应用层,是应用层协议。浏览器访问网页使用http协议来进行数据的传输,使用HTTP协议时,客户端首先与服务端的80(默认)端口建立一个TCP连接,然后在这个连接的基础上进行请求和应答,以及数据的交换,数据可以是HTML文件, 图片文件, 查询结果等。
HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。HTTP有三个常用版本,分别是1.0、1.1和2。主要区别在于HTTP1.0中每次请求和应答都会使用一个新的TCP连接,而从HTTP1.1开始,运行在一个TCP连接上发送多个请求和应答。因此大幅度减少了TCP连接的建立和断开,提高了效率。HTTP2在1.1的基础上改进协议的一些特点,包括长连接、pipeline、并行连接等。
HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。基本模式如下图:
简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
支持B/S及C/S模式。
在介绍HTTP协议的格式之前,我们先来看看URL和URI。这俩个概念经常被弄混。
首先,什么是URI呢?URI,全称为uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。
Web上可用的每种资源-HTML文档、图像、视频片段、程序等都由一个通用资源标识符(即URI)进行定位。
URI一般由三部分组成:
比如下面这个URI例子:http://www.dodomonster.com/html/html4
这个URI定义如下:是一个通过HTTP协议访问的资源,位于www.dodomonster.com上,通过路径"/html/html4"访问。
有的URI指向一个资源的内部。这种URi以”#”结束,并跟着一个anchor标识符(称为片段标识符)。例如,下面是一个指向section_2的URI:http://somesite.com/html/top.htm#section_2
绝对URi
URI有绝对和相对之分,绝对的URI指以scheme(后面跟着冒号)开头的URi。前面提到的http://www.cnn.com
就是绝对的URI的一个例子,其它的例子还有mailto:[email protected]
、news:comp.lang.java.help
和xyz://whatever
。你可以把绝对的URi看作是以某种方式引用某种资源,而这种方式对标识符出现的环境没有依赖。如果使用文件系统作类比,绝对的URI类似于从根目录开始的某个文件的径。
相对URi
相对URI不包含任何命名规范信息,它的路径通常指同一台机器上的资源。相对URI可能含有相对路径(如,”…”表示上一层路径),还可能包含片段标识符。
为了说明相对URI,此处举一个例子,假设在一个HTML页面地址是:http://www.dodomonster.com/support/index.htm
里有一张图片,地址是<img src="../icons/logo.png" alt="logo">
。此地址就是相对地址,它扩展成完全的URi就是http://www.dodomonster.com/icons/logo.png
与绝对的URI不同的,相对的URI不是以scheme(后面跟着冒号)开始。可以把相对的URI看作是以某种方式引用某种资源,而这种方式依赖于标识符出现的环境。如果用文件系统作类比,相对的URI类似于从当前目录开始的文件路径。
URL全程是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL不仅可以用来标识一个资源,而且还指明了如何去定位这个资源。通俗地说,URL是Internet上用来描述资源的字符串,主要用在各种www客户端和服务器程序,特别是著名的Mosaic。采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。
URL的第一个部分http://
表示要访问的文件的类型。在网上,这几乎总是使用http(超文本传输协议,hypertext transfer protocol-用来转换网页的协议);有时也使用ftp(文件传输协议,file transfer protocol-用来传输软件和大文件;telnet(远程登录),主要用于远程交谈以及文件调用等,意思是浏览器正在阅读本地盘外的一个文件而不是一个远程计算机。
URL组成
Internet资源类型(schema):指出www客户程序用来C作的工具。如http://
表示www服务器,ftp://
表示ftp服务器,gopher://
表示Gopher服务器,而new:
表示Newgroup新闻组。必需的。
服务器地址(host):指出www网页所在的服务器域名。必需的。
端口(port):对某些资源的访问来说,需给出相应的服务器提供端口。可选的。
路径(path):指明服务器上某资源的位置。与端口一样,路径并非总是需要的。可选的。
URL地址格式排列为:schema://host:port/path
,如:http://www.maogoo.com/bbs
客户程序首先看到http(超文本协议),便知道处理的是HTML链接。接下来的wwww.maogoo.com是站点地址,最后是目录/bbs。
必须注意:www上的服务器都是区分大小写的,所以千万要注意正确的URL大小写表达形式。
在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信息,因此它不能是相对的。 在Java类库中,URI类不包含任何访问资源的方法,它唯一的作用就是解析。相反的是,URL类可以打开一个到达资源的流。
大致的格式如下:
请求报文包含四部分:
响应报文包含四部分:
请求的格式如下图:
下面以一个具体的例子来解释请求的消息格式,
1 | GET /562f25980001b1b106000338.jpg HTTP/1.1 |
请求行:表示请求类型,请求的资源地址,以及使用的HTTP协议版本号,分别以空格隔开,以换行符表示结束。对应上面的例子可以看出,请求类型为GET,[/562f25980001b1b106000338.jpg]为要访问的资源,该行的最后一部分说明使用的是HTTP1.1版本。
请求头部字段::用来说明服务器要使用的附加信息。每一行是一个附加信息,以换行符来区别不同的附加信息,然后每一行中通过空格来区别开key值和value。从第二行起为请求头部,从上面的例子可以得出以下的信息,
空行::请求头部后面的空行是必须的,即使第四部分的请求数据为空,也必须有空行。代表头部的结束。
请求内容::可以添加任意的内容。
POST请求例子,使用Charles抓取的request:
1 | POST / HTTP1.1 |
第一部分:请求行,第一行明了是post请求,以及http1.1版本。
第二部分:请求头部,第二行至第六行。
第三部分:空行,第七行的空行。
第四部分:请求数据,第八行。
一般情况下,服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应消息。
一个简单的响应的例子:
1 | HTTP/1.1 200 OK |
状态行:由HTTP协议版本号,状态码,状态消息 三部分组成,主要用来表示响应消息的状态。上面的例子第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok)
消息报头:表示表示响应的一些信息,比如编码,时间等等。要使用的一些附加信息,和前面的请求数据一样,使用换行符来区别不同的附加信息,然后每一行通过空格来区分开key值和value。上面例子的第二行和第三行为消息报
空行:消息报头后面的空行是必须的,用于区别开响应的头部信息和数据部分信息
响应正文: 服务器返回给客户端的文本信息。上面的例子返回的是html文本。
根据HTTP标准,HTTP请求可以使用多种请求方法。
HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。
HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
这里简答介绍GET和POST方法的区别
首先通过一个具体的请求来先了解他们的区别,然后在进行总结
GET请求
1 | GET /books/?sex=man&name=Professional HTTP/1.1 |
注意最后一行是空行
POST请求
1 | POST / HTTP/1.1 |
GET和POST的区别
此外Http协议定义了很多与服务器交互的方法,最基本的有4种,分别是GET,POST,PUT,DELETE. 一个URL地址用于描述一个网络上的资源,而HTTP中的GET, POST, PUT, DELETE就对应着对这个资源的查,改,增,删4个操作。 我们最常见的就是GET和POST了。GET一般用于获取/查询资源信息,而POST一般用于更新资源信息。其中这一块还有一个幂等性的概念,这个会单独写一篇文章来解释。
当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览器的请求。
HTTP状态码的英文为HTTP Status Code。
下面是常见的HTTP状态码:
HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型:
分类 | 分类描述 |
---|---|
1** | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作以完成请求 |
4** | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
HTTP状态码列表:
状态码 | 状态码英文名称 | 中文描述 |
---|---|---|
100 | Continue | 继续。客户端应继续其请求 |
101 | Switching Protocols | 切换协议。服务器根据客户端的请求切换协议。 只能切换到更高级的协议,例如,切换到HTTP的新版本协议 |
200 | OK | 请求成功。一般用于GET与POST请求 |
201 | Created | 已创建。成功请求并创建了新的资源 |
202 | Accepted | 已接受。已经接受请求,但未处理完成 |
203 | Non-Authoritative Information | 非授权信息。请求成功。但返回的meta信息不在原始的服务器, 而是一个副本 |
204 | No Content | 无内容。服务器成功处理,但未返回内容。 在未更新网页的情况下,可确保浏览器继续显示当前文档 |
205 | Reset Content | 重置内容。服务器处理成功, 用户终端(例如:浏览器)应重置文档视图。 可通过此返回码清除浏览器的表单域 |
206 | Partial Content | 部分内容。服务器成功处理了部分GET请求 |
300 | Multiple Choices | 多种选择。请求的资源可包括多个位置, 相应可返回一个资源特征与地址的列表 用于用户终端(例如:浏览器)选择 |
301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新URI, 返回信息会包括新的URI, 浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
302 | Found | 临时移动。与301类似。但资源只是临时被移动。 客户端应继续使用原有URI |
303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时, 不会返回任何资源。客户端通常会缓存访问过的资源, 通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
305 | Use Proxy | 使用代理。所请求的资源必须通过代理访问 |
306 | Unused | 已经被废弃的HTTP状态码 |
307 | Temporary Redirect | 临时重定向。与302类似。使用GET请求重定向 |
400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
401 | Unauthorized | 请求要求用户的身份认证 |
402 | Payment Required | 保留,将来使用 |
403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置”您所请求的资源无法找到”的个性页面 |
405 | Method Not Allowed | 客户端请求中的方法被禁止 |
406 | Not Acceptable | 服务器无法根据客户端请求的内容特性完成请求 |
407 | Proxy Authentication Required | 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 |
408 | Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
409 | Conflict | 服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突 |
410 | Gone | 客户端请求的资源已经不存在。410不同于404, 如果资源以前有现在被永久删除了可使用410代码, 网站设计人员可通过301代码指定资源的新位置 |
411 | Length Required | 服务器无法处理客户端发送的不带Content-Length的请求信息 |
412 | Precondition Failed | 客户端请求信息的先决条件错误 |
413 | Request Entity Too Large | 由于请求的实体过大,服务器无法处理, 因此拒绝请求。为防止客户端的连续请求, 服务器可能会关闭连接。如果只是服务器暂时无法处理, 则会包含一个Retry-After的响应信息 |
414 | Request-URI Too Large | 请求的URI过长(URI通常为网址),服务器无法处理 |
415 | Unsupported Media Type | 服务器无法处理请求附带的媒体格式 |
416 | Requested range not satisfiable | 客户端请求的范围无效 |
417 | Expectation Failed | 服务器无法满足Expect的请求头信息 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 |
501 | Not Implemented | 服务器不支持请求的功能,无法完成请求 |
502 | Bad Gateway | 作为网关或者代理工作的服务器尝试执行请求时, 从远程服务器接收到了一个无效的响应 |
503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。 延时的长度可包含在服务器的Retry-After头信息中 |
504 | Gateway Time-out | 充当网关或代理的服务器,未及时从远端服务器获取请求 |
505 | HTTP Version not supported | 服务器不支持请求的HTTP协议的版本,无法完成处理 |
前文介绍过TCP是一种可靠的协议,主要通过超时重传管理、窗口管理、流量控制、拥塞控制来保证其可靠性。本文将对这些概念进行详细介绍,主要分文三大部分,重传,流量控制,拥塞控制。
注:以下内容节选自《高性能浏览器网络》(出版社:O’Reilly,作者:Ilya Grigorik)。 要了解完整版本和相关内容,请访问 hpbn.co。
HTTP/2 可以让我们的应用更快、更简单、更稳定 - 这几词凑到一块是很罕见的!HTTP/2 将很多以前我们在应用中针对 HTTP/1.1 想出来的“歪招儿”一笔勾销,把解决那些问题的方案内置在了传输层中。 不仅如此,它还为我们进一步优化应用和提升性能提供了全新的机会!
HTTP/2 的主要目标是通过支持完整的请求与响应复用来减少延迟,通过有效压缩 HTTP 标头字段将协议开销降至最低,同时增加对请求优先级和服务器推送的支持。 为达成这些目标,HTTP/2 还给我们带来了大量其他协议层面的辅助实现,例如新的流控制、错误处理和升级机制。上述几种机制虽然不是全部,但却是最重要的,每一位网络开发者都应该理解并在自己的应用中加以利用。
HTTP/2 没有改动 HTTP 的应用语义。 HTTP 方法、状态代码、URI 和标头字段等核心概念一如往常。 不过,HTTP/2 修改了数据格式化(分帧)以及在客户端与服务器间传输的方式。这两点统帅全局,通过新的分帧层向我们的应用隐藏了所有复杂性。 因此,所有现有的应用都可以不必修改而在新协议下运行。
为什么不是 HTTP/1.2?
为了实现 HTTP 工作组设定的性能目标,HTTP/2 引入了一个新的二进制分帧层,该层无法与之前的 HTTP/1.x 服务器和客户端向后兼容,因此协议的主版本提升到 HTTP/2。
即便如此,除非您在实现网络服务器(或自定义客户端),需要使用原始的 TCP 套接字,否则您很可能注意不到任何区别:所有新的低级分帧由客户端和服务器为您执行。 可观察到的唯一区别将是性能的提升和请求优先级、流控制与服务器推送等新功能的出现。
SPDY 是 Google 开发的一个实验性协议,于 2009 年年中发布,其主要目标是通过解决 HTTP/1.1 中广为人知的一些性能限制来减少网页的加载延迟。具体来说,这个项目设定的目标如下:
注:为了达到减少 50% 页面加载时间的目标,SPDY 引入一个新的二进制分帧层,以实现请求和响应复用、优先级和标头压缩,目的是更有效地利用底层 TCP 连接;请参阅延迟是性能瓶颈。
首次发布后不久,Google 的两位软件工程师 Mike Belshe 和 Roberto Peon 就分享了他们对这个新实验性 SPDY 协议的实现结果、文档和源代码:
目前为止,我们只在实验室条件下测试过 SPDY。 最初的成果 很激动人心:通过模拟的家庭网络 连接下载了 25 个最流行的网站之后,我们发现性能的提升特别明显,页面 加载速度最高加快了 55%。 (Chromium 博客)
到了 2012 年,这个新的实验性协议得到 Chrome、Firefox 和 Opera 的支持,而且越来越多的大型网站(如 Google、Twitter、Facebook)和小型网站开始在其基础设施内部署 SPDY。 事实上,在被行业越来越多的采用之后,SPDY 已经具备了成为一个标准的条件。
观察到这一趋势后,HTTP 工作组 (HTTP-WG) 将这一工作提上议事日程,吸取 SPDY 的经验教训,并在此基础上制定了官方“HTTP/2”标准。 在拟定宣言草案、向社会征集 HTTP/2 建议并经过内部讨论之后,HTTP-WG 决定将 SPDY 规范作为新 HTTP/2 协议的基础。
在接下来几年中,SPDY 和 HTTP/2 继续共同演化,其中 SPDY 作为实验性分支,用于为 HTTP/2 标准测试新功能和建议。 理论不一定适合实践(反之亦然),SPDY 提供一个测试和评估路线,可以对要纳入 HTTP/2 标准中的每条建议进行测试和评估。 最终,这个过程持续了三年,期间产生了十余个中间草案:
2015 年初,IESG 审阅了新的 HTTP/2 标准并批准发布。 之后不久,Google Chrome 团队公布了他们为 TLS 弃用 SPDY 和 NPN 扩展的时间表:
与 HTTP/1.1 相比,HTTP/2 的主要变化在于性能提升。 > 一些主要功能(例如复用、标头压缩、优先级和协议协商)演化自之前开放但不标准的协议 (SPDY)。 Chrome 自 Chrome 6 开始就支持 SPDY,但由于大部分优点都集中在 HTTP/2 中,是时候向 SPDY 说再见了。 我们计划于 2016 年初停止对 SPDY 的支持,还会停止对 TLS 的 NPN 扩展的支持,转而在 Chrome 中使用 ALPN。
强烈建议服务器开发者迁移到 HTTP/2 和 ALPN。 我们很高兴参与到最终催生了 HTTP/2 的开放式标准的制定过程,并且考虑到整个行业在标准化和实现过程中的参与热情,我们希望对这一标准的采纳越来越多。 (Chromium> 博客)
SPDY 与 HTTP/2 的共同演化让服务器、浏览器和网站开发者可以在新协议制定过程中获得真实体验。 因此,HTTP/2 标准自诞生之日起就成为最好并经过大量测试的标准之一。 到 HTTP/2 被 IESG 批准时,已经有很多经过完全测试并且可以立即投入生产的客户端与服务器。 事实上,在最终协议被批准的几周后,由于多款热门浏览器(和许多网站)都部署了完整的 HTTP/2 支持,大量用户都体会到了新协议的好处。
早期版本的 HTTP 协议的设计初衷主要是实现要简单: HTTP/0.9 只用一行协议就启动了万维网;HTTP/1.0 则是对流行的 HTTP/0.9 扩展的一个正式说明;HTTP 1.1 则是 IETF 的一份官方标准;请参阅 HTTP 简史。 因此,HTTP/0.9-1.x 实现了其目的:HTTP 是应用最广泛、采用最多的一个互联网应用协议。
然而,实现简单是以牺牲应用性能为代价的: HTTP/1.x 客户端需要使用多个连接才能实现并发和缩短延迟;HTTP/1.x 不会压缩请求和响应标头,从而导致不必要的网络流量;HTTP/1.x 不支持有效的资源优先级,致使底层 TCP 连接的利用率低下;等等。
这些限制并不是致命的,但是随着网络应用的范围、复杂性以及在我们日常生活中的重要性不断增大,它们对网络开发者和用户都造成了巨大负担,而这正是 HTTP/2 要致力于解决的:
HTTP/2 通过支持标头字段压缩和在同一连接上 进行多个并发交换,让应用更有效地利用网络资源,减少 感知的延迟时间。具体来说,它可以对同一连接上的请求和响应消息进行交错 发送并为 HTTP 标头字段使用 有效编码。 > HTTP/2 还允许为请求设置优先级,让更重要的请求更快速地完成,从而进一步 提升性能。
出台的协议更有利于网络,因为与 HTTP/1.x 相比,可以使用更少的 TCP 连接。 > 这意味着与其他流的竞争减小,并且连接的持续时间变长,这些特性反过来提高 了可用网络容量的利用率。 最后,HTTP/2 还可以通过使用二进制消息分帧对消息进行更高效 的处理。 (超文本传输协议版本 2,草案 17)
需要注意的是,HTTP/2 仍是对之前 HTTP 标准的扩展,而非替代。 HTTP 的应用语义不变,提供的功能不变,HTTP 方法、状态代码、URI 和标头字段等这些核心概念也不变。 这些方面的变化都不在 HTTP/2 考虑之列。 虽然高级 API 保持不变,仍有必要了解低级变更如何解决了之前协议的性能限制。 我们来简单了解一下二进制分帧层及其功能。
HTTP/2 所有性能增强的核心在于新的二进制分帧层,它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。
这里所谓的“层”,指的是位于套接字接口与应用可见的高级 HTTP API 之间一个经过优化的新编码机制:HTTP 的语义(包括各种动词、方法、标头)都不受影响,不同的是传输期间对它们的编码方式变了。 HTTP/1.x 协议以换行符作为纯文本的分隔符,而 HTTP/2 将所有传输的信息分割为更小的消息和帧,并采用二进制格式对它们编码。
这样一来,客户端和服务器为了相互理解,都必须使用新的二进制编码机制:HTTP/1.x 客户端无法理解只支持 HTTP/2 的服务器,反之亦然。 不过不要紧,现有的应用不必担心这些变化,因为客户端和服务器会替我们完成必要的分帧工作。
新的二进制分帧机制改变了客户端与服务器之间交换数据的方式。 为了说明这个过程,我们需要了解 HTTP/2 的三个概念:
这些概念的关系总结如下:
简言之,HTTP/2 将 HTTP 协议通信分解为二进制编码帧的交换,这些帧对应着特定数据流中的消息。所有这些都在一个 TCP 连接内复用。 这是 HTTP/2 协议所有其他功能和性能优化的基础。
在 HTTP/1.x 中,如果客户端要想发起多个并行请求以提升性能,则必须使用多个 TCP 连接(请参阅使用多个 TCP 连接)。 这是 HTTP/1.x 交付模型的直接结果,该模型可以保证每个连接每次只交付一个响应(响应排队)。 更糟糕的是,这种模型也会导致队首阻塞,从而造成底层 TCP 连接的效率低下。
HTTP/2 中新的二进制分帧层突破了这些限制,实现了完整的请求和响应复用:客户端和服务器可以将 HTTP 消息分解为互不依赖的帧,然后交错发送,最后再在另一端把它们重新组装起来。
快照捕捉了同一个连接内并行的多个数据流。 客户端正在向服务器传输一个 DATA
帧(数据流 5),与此同时,服务器正向客户端交错发送数据流 1 和数据流 3 的一系列帧。因此,一个连接上同时有三个并行数据流。
将 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装是 HTTP 2 最重要的一项增强。事实上,这个机制会在整个网络技术栈中引发一系列连锁反应,从而带来巨大的性能提升,让我们可以:
HTTP/2 中的新二进制分帧层解决了 HTTP/1.x 中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。 结果,应用速度更快、开发更简单、部署成本更低。
将 HTTP 消息分解为很多独立的帧之后,我们就可以复用多个数据流中的帧,客户端和服务器交错发送和传输这些帧的顺序就成为关键的性能决定因素。 为了做到这一点,HTTP/2 标准允许每个数据流都有一个关联的权重和依赖关系:
数据流依赖关系和权重的组合让客户端可以构建和传递“优先级树”,表明它倾向于如何接收响应。 反过来,服务器可以使用此信息通过控制 CPU、内存和其他资源的分配设定数据流处理的优先级,在资源数据可用之后,带宽分配可以确保将高优先级响应以最优方式传输至客户端。
HTTP/2 内的数据流依赖关系通过将另一个数据流的唯一标识符作为父项引用进行声明;如果忽略标识符,相应数据流将依赖于“根数据流”。 声明数据流依赖关系指出,应尽可能先向父数据流分配资源,然后再向其依赖项分配资源。 换句话说,“请先处理和传输响应 D,然后再处理和传输响应 C”。
共享相同父项的数据流(即,同级数据流)应按其权重比例分配资源。 例如,如果数据流 A 的权重为 12,其同级数据流 B 的权重为 4,那么要确定每个数据流应接收的资源比例,请执行以下操作:
4 + 12 = 16
A = 12/16, B = 4/16
因此,数据流 A 应获得四分之三的可用资源,数据流 B 应获得四分之一的可用资源;数据流 B 获得的资源是数据流 A 所获资源的三分之一。
我们来看一下上图中的其他几个操作示例。 从左到右依次为:
如上面的示例所示,数据流依赖关系和权重的组合明确表达了资源优先级,这是一种用于提升浏览性能的关键功能,网络中拥有多种资源类型,它们的依赖关系和权重各不相同。 不仅如此,HTTP/2 协议还允许客户端随时更新这些优先级,进一步优化了浏览器性能。 换句话说,我们可以根据用户互动和其他信号更改依赖关系和重新分配权重。
注:数据流依赖关系和权重表示传输优先级,而不是要求,因此不能保证特定的处理或传输顺序。 即,客户端无法强制服务器通过数据流优先级以特定顺序处理数据流。 尽管这看起来违反直觉,但却是一种必要行为。 我们不希望在优先级较高的资源受到阻止时,还阻止服务器处理优先级较低的资源。
有了新的分帧机制后,HTTP/2 不再依赖多个 TCP 连接去并行复用数据流;每个数据流都拆分成很多帧,而这些帧可以交错,还可以分别设定优先级。 因此,所有 HTTP/2 连接都是永久的,而且仅需要每个来源一个连接,随之带来诸多性能优势。
SPDY 和 HTTP/2 的杀手级功能是,可以在一个拥塞受到良好控制的通道上任意进行复用。 这一功能的重要性和良好运行状况让我吃惊。 我喜欢的一个非常不错的指标是连接拆分,这些拆分仅承载一个 HTTP 事务(并因此让该事务承担所有开销)。 对于 HTTP/1,我们 74% 的活动连接仅承载一个事务 - 永久连接并不如我们所有人希望的那般有用。 但是在 HTTP/2 中,这一比例锐减至 25%。 这是在减少开销方面获得的巨大成效。 (HTTP/2 登陆 Firefox,Patrick McManus)
大多数 HTTP 传输都是短暂且急促的,而 TCP 则针对长时间的批量数据传输进行了优化。 通过重用相同的连接,HTTP/2 既可以更有效地利用每个 TCP 连接,也可以显著降低整体协议开销。 不仅如此,使用更少的连接还可以减少占用的内存和处理空间,也可以缩短完整连接路径(即,客户端、可信中介和源服务器之间的路径) 这降低了整体运行成本并提高了网络利用率和容量。 因此,迁移到 HTTP/2 不仅可以减少网络延迟,还有助于提高通量和降低运行成本。
注:连接数量减少对提升 HTTPS 部署的性能来说是一项特别重要的功能:可以减少开销较大的 TLS 连接数、提升会话重用率,以及从整体上减少所需的客户端和服务器资源。
流控制是一种阻止发送方向接收方发送大量数据的机制,以免超出后者的需求或处理能力:发送方可能非常繁忙、处于较高的负载之下,也可能仅仅希望为特定数据流分配固定量的资源。 例如,客户端可能请求了一个具有较高优先级的大型视频流,但是用户已经暂停视频,客户端现在希望暂停或限制从服务器的传输,以免提取和缓冲不必要的数据。 再比如,一个代理服务器可能具有较快的下游连接和较慢的上游连接,并且也希望调节下游连接传输数据的速度以匹配上游连接的速度来控制其资源利用率;等等。
上述要求会让您想到 TCP 流控制吗?您应当想到这一点;因为问题基本相同(请参阅流控制)。 不过,由于 HTTP/2 数据流在一个 TCP 连接内复用,TCP 流控制既不够精细,也无法提供必要的应用级 API 来调节各个数据流的传输。 为了解决这一问题,HTTP/2 提供了一组简单的构建块,这些构建块允许客户端和服务器实现其自己的数据流和连接级流控制:
DATA
帧时都会减小,在接收方发出 WINDOW_UPDATE
帧时增大。SETTINGS
帧,这会在两个方向上设置流控制窗口。 流控制窗口的默认值设为 65,535 字节,但是接收方可以设置一个较大的最大窗口大小(2^31-1
字节),并在接收到任意数据时通过发送 WINDOW_UPDATE
帧来维持这一大小。HTTP/2 未指定任何特定算法来实现流控制。 不过,它提供了简单的构建块并推迟了客户端和服务器实现,可以实现自定义策略来调节资源使用和分配,以及实现新传输能力,同时提升网页应用的实际性能和感知性能(请参阅速度、性能和人类感知)。
例如,应用层流控制允许浏览器仅提取一部分特定资源,通过将数据流流控制窗口减小为零来暂停提取,稍后再行恢复。 换句话说,它允许浏览器提取图像预览或首次扫描结果,进行显示并允许其他高优先级提取继续,然后在更关键的资源完成加载后恢复提取。
HTTP/2 新增的另一个强大的新功能是,服务器可以对一个客户端请求发送多个响应。 换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源(图 12-5),而无需客户端明确地请求。
注:HTTP/2 打破了严格的请求-响应语义,支持一对多和服务器发起的推送工作流,在浏览器内外开启了全新的互动可能性。 这是一项使能功能,对我们思考协议、协议用途和使用方式具有重要的长期影响。
为什么在浏览器中需要一种此类机制呢?一个典型的网络应用包含多种资源,客户端需要检查服务器提供的文档才能逐个找到它们。 那为什么不让服务器提前推送这些资源,从而减少额外的延迟时间呢? 服务器已经知道客户端下一步要请求什么资源,这时候服务器推送即可派上用场。
事实上,如果您在网页中内联过 CSS、JavaScript,或者通过数据 URI 内联过其他资产(请参阅资源内联),那么您就已经亲身体验过服务器推送了。 对于将资源手动内联到文档中的过程,我们实际上是在将资源推送给客户端,而不是等待客户端请求。 使用 HTTP/2,我们不仅可以实现相同结果,还会获得其他性能优势。 推送资源可以进行以下处理:
所有服务器推送数据流都由 PUSH_PROMISE
帧发起,表明了服务器向客户端推送所述资源的意图,并且需要先于请求推送资源的响应数据传输。 这种传输顺序非常重要:客户端需要了解服务器打算推送哪些资源,以免为这些资源创建重复请求。 满足此要求的最简单策略是先于父响应(即,DATA
帧)发送所有 PUSH_PROMISE
帧,其中包含所承诺资源的 HTTP 标头。
在客户端接收到 PUSH_PROMISE
帧后,它可以根据自身情况选择拒绝数据流(通过 RST_STREAM
帧)。 (例如,如果资源已经位于缓存中,便可能会发生这种情况。) 这是一个相对于 HTTP/1.x 的重要提升。 相比之下,使用资源内联(一种受欢迎的 HTTP/1.x“优化”)等同于“强制推送”:客户端无法选择拒绝、取消或单独处理内联的资源。
使用 HTTP/2,客户端仍然完全掌控服务器推送的使用方式。 客户端可以限制并行推送的数据流数量;调整初始的流控制窗口以控制在数据流首次打开时推送的数据量;或完全停用服务器推送。 这些优先级在 HTTP/2 连接开始时通过 SETTINGS
帧传输,可能随时更新。
推送的每个资源都是一个数据流,与内嵌资源不同,客户端可以对推送的资源逐一复用、设定优先级和处理。 浏览器强制执行的唯一安全限制是,推送的资源必须符合原点相同这一政策:服务器对所提供内容必须具有权威性。
每个 HTTP 传输都承载一组标头,这些标头说明了传输的资源及其属性。 在 HTTP/1.x 中,此元数据始终以纯文本形式,通常会给每个传输增加 500–800 字节的开销。如果使用 HTTP Cookie,增加的开销有时会达到上千字节。 (请参阅测量和控制协议开销。) 为了减少此开销和提升性能,HTTP/2 使用 HPACK 压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:
利用霍夫曼编码,可以在传输时对各个值进行压缩,而利用之前传输值的索引列表,我们可以通过传输索引值的方式对重复值进行编码,索引值可用于有效查询和重构完整的标头键值对。
作为一种进一步优化方式,HPACK 压缩上下文包含一个静态表和一个动态表:静态表在规范中定义,并提供了一个包含所有连接都可能使用的常用 HTTP 标头字段(例如,有效标头名称)的列表;动态表最初为空,将根据在特定连接内交换的值进行更新。 因此,为之前未见过的值采用静态 Huffman 编码,并替换每一侧静态表或动态表中已存在值的索引,可以减小每个请求的大小。
注:在 HTTP/2 中,请求和响应标头字段的定义保持不变,仅有一些微小的差异:所有标头字段名称均为小写,请求行现在拆分成各个 :method
、:scheme
、:authority
和 :path
伪标头字段。
早期版本的 HTTP/2 和 SPDY 使用 zlib(带有一个自定义字典)压缩所有 HTTP 标头。 这种方式可以将所传输标头数据的大小减小 85% - 88%,显著减少了页面加载时间延迟:
在带宽较低的 DSL 链路中,上行链路速度仅有 375 Kbps,仅压缩请求标头就显著减少了特定网站(即,发出大量资源请求的网站)的页面加载时间。 我们发现,仅仅由于标头压缩,页面加载时间就减少了 45 - 1142 毫秒。 (SPDY 白皮书, chromium.org)
然而,2012 年夏天,出现了针对 TLS 和 SPDY 压缩算法的“犯罪”安全攻击,此攻击会导致会话被劫持。 于是,zlib 压缩算法被 HPACK 替代,后者经过专门设计,可以解决发现的安全问题、实现起来也更高效和简单,当然,可以对 HTTP 标头元数据进行良好压缩。
如需了解有关 HPACK 压缩算法的完整详情,请参阅 IETF HPACK - HTTP/2 的标头压缩。
在TCP简介文章中介绍过,TCP是面向连接的协议,因此需要对连接进行管理,但是连接管理不止包括连接的建立和释放,还需要检测连接的双方正常,及TCP保活机制要做的内容。本篇文章将会首先介绍什么保活机制以及为什么需要保活机制,然后介绍保活机制的作用,最后介绍保活机制的原理。
保活机制是一种在不影响数据流内容的情况下探测对方是否存活的方式。
保活机制不是TCP规范中的一部分。原因主要有以下三点。
然而,大部分TCP的实现方,都提供了保活机制。保活功能在默认情况下是关闭的,TCP连接的任何一端都可以请求打开这一功能。保活功能可以被设置在连接的一端、两端或者两端都没有。
tcp是面向连接的,在通信前会进行三次握手,在断开连接时会进行四次挥手。本篇文章会对连接的具体过程进行详细介绍,同时也会深入挖掘一些与连接相关的问题进行说明。
第一次握手:客户端发送syn包(seq=x)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(seq=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
注意:tcp是全双工通信,客户端和服务器端都可以发送和接收数据,所以发送端和接收端都各自有一个序列号,也就是上面的x和y值
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。