作者: 黄翠刚 转载请留下转载地址 , 谢谢!
说明: 本文对HTTP1.0版本的描述不会太多 , 更多关注1.1和2.0版本 . 参考资料来源于互联网 , 而非完整阅读HTTP标准协议后产生的认知 , 某些观点与标准协议提出的标准理念可能会存在认知偏差 , 欢迎广大网友提出并纠正不正确的地方 , 大家一起成长 .
参考资料:
互联网RFC / STD / FYI / BCP档案 : 宝藏级 , 各种标准协议都可以在这里找到
HTTP1.1标准协议RFC2616 : 请直接定位到章节19.6.1 , 这个章节是标准协议描述的HTTP1.1与1.0主要的区别.
一.HTTP的主要版本
HTTP1.0 / 1.1 / 2.0 , 其中HTTP1.0是1996年出现首次使用的 , 1999年发布了HTTP1.1 , 使用至今 , HTTP1.1依然是目前使用的较为主流的HTTP协议版本 , 但是早在2015年有着重大升级的HTTP2.0才是目前最优的一个版本 . 下面我们看看个版本的区别
二.HTTP的基本优化
影响HTTP网络请求的因素主要有两个:宽带 和 延迟
宽带: 时至当下 , 动辄每秒上10M的网络宽带传输速度 , 这个对于HTTP请求的影响 , 已经不用考虑了
延迟: 主要是 浏览器阻塞 , DNS域名解析 , 建立TCP连接
三.HTTP/1.0 与 1.1 的差别
HTTP1.0版本是HTTP协议广泛使用的第1个版本 , 1996年出现在市场上被广泛使用 , 随着互联网的加速发展 , HTTP1.0的毛病就显露出来了 , 所以才有了1999年发布的HTTP1.1版本.
对比1.0版本 , 1.1引入了新的特性: (先整理一个目录 , 方便索引对应的章节)
3.1.Persistent Connection持久连接(可以说是新增,也可以说是优化)
3.2. (新增)流水线(Pipelining)处理
3.3.HOST域
3.4.dete/timestamp(日期/时间戳)
3.5.Transfer Coding
3.6.质量值(Quality Values)
3.7.缓存处理(Etag , If-Unmodified-Since, If-Match, If-None-Match等 )
3.8.节约优化
3.9.新增状态码(错误通知)
3.1.Persistent Connection持久连接(可以说是新增,也可以说是优化)
在 "TCP/IP协议" 中 , HTTP协议是属于 "OSI 7层参考模型" 的 "应用层" 的协议 , 在 "网络层" 的 "TCP协议" 之上运行 , 一个HTTP请求的必要流程 : 3次握手建立TCP连接 , 发送HTTP请求 , 在HTTP1.0中 , 每对Request / Response 都使用一个新的TCP连接 , HTTP1.1则支持Persistent Connection持久连接 , 并默认使用Persistent Connection , 在同一个TCP连接中可以传送多个HTTP请求和响应.
可以想象一下 , HTTP1.0协议 , 打开一个网址www.huancuigang.cn , 首页上的js , css , 图片 , api接口等等HTTP请求也许30个 , 那么这30个请求都得建立30次TCP连接(3次握手) , 也会进行30次断开连接(4次分手) . HTTP1.1 就能复用TCP连接 , 30个HTTP请求可能就复用1个TCP连接就行了
当然 , HTTP1.0也是支持持久链接的 , 需要手动的加入Connection:Keep-Alive请求头信息 , 并且是默认关闭的 , 而HTTP1.1则是默认开启使用 Persistent Connection持久连接 , 这也是这两个版本在使用持久连接的区别 .
注意 , 这里的名词 , HTTP1.0是用Keep-Alive来做持久连接的 , 但是到了HTTP1.1时 , 就是使用的Persistent Connection持久连接了 , 很多人都说HTTP1.1默认开启了Keep-Alive .
疑惑1: 我很疑惑 HTTP1.0用的Keep-Alive和 HTTP1.1用的Persistent Connection的区别是什么 , 它们到底是不是同一个东西 ?
解惑1: 我们可以在HTTP1.1标准协议RFC2616 和 HTTP1.0标准协议RFC2145 , 包括TCP标准协议RFC793中找找关于Keep-Alive的相关描述 , HTTP1.0标准协议RFC2145 和 TCP标准协议RFC793中都是没有对Keep-Alive的任何相关描述 ,
到这里 , 我终于想通了一件事情 , HTTP协议标准和HTTP协议实现 , 是两个维度的事物 , HTTP协议实现是基于HTTP协议标准的 , 但是HTTP协议实现 , 也有实现一些不在协议标准之内的操作 , 比如这里说的HTTP1.0可以通过手动的加入Connection:Keep-Alive请求头信息来使用长连接 , 实际上HTTP1.0标准协议本身并没有提出这个协议约定 , 是HTTP协议实现的应用程序 , 比如nginx , apache 等等实现HTTP1.0协议的应用程序需要实现长连接的功能 , 提出来的一种解决方案 , 并且形成了不成文的约定 , 大家都这样去用了 , 并且在HTTP1.1标准协议RFC2616中确实也有提到HTTP1.0持久连接使用Keep-Alive的一些描述 , 也描述了这样HTTP1.0使用Keep-Alive请求头所带来的问题 , 比如HTTP1.1标准协议RFC2616中的19.7.1章节 与HTTP / 1.0持久连接的兼容性 , 这里也说明了一些问题
回到疑惑的主题 , HTTP1.0用的Keep-Alive和 HTTP1.1用的Persistent Connection的区别 , HTTP1.0用的Keep-Alive本身不属于HTTP1.0标准协议RFC2145的协力内容 , 协议的实现有提供这种操作 , 可能本身就是属于实验性质的实现 , 并没有体现在标准协议当中 , 而时隔3年的HTTP1.1用的Persistent Connection持久连接 , 则是专门优化HTTP1.0用的Keep-Alive而出现的新特性 , 协议它本身也不能处理任何数据 , 协议只是规定是数据的格式规范和标准 , 大家都按照协议的格式规范和标准来传递消息 , 才能让双方明白对方再说什么而已 , 所以HTTP1.0用的Keep-Alive和 HTTP1.1用的Persistent Connection , 它们在更低一个协议层还是TCP协议的实现提供的具体的保持连接的功能 , 所以从某种意义上来说 , 可以说它们是同一个东西 , 只是HTTP1.1标准协议RFC2616中明确约定了默认保持连接了 , 并且比HTTP1.0用的Keep-Alive更好更安全而已 , 但要说他们不是同一个东西也行 , 因为你Keep-Alive只是一种实现方式 , HTTP1.1默认保持长连接又是另外一种方式 , 可能实现的代码都完全不一样 , 只是大家都是实现的基本相同的功能而已 . (个人见解 , 欢迎指正)
我们这里谈到的认知 , 可能也只是冰山一角 , 比如 , nginx和apache在实现HTTP1.0协议时 , 接收Keep-Alive请求头后是怎么处理的? 以及怎么调用Linux内核中TCP协议的相关程序来完成长连接的? 还有 , HTTP1.0的Keep-Alive和HTTP1.1的默认保持连接在真正调用TCP协议实现的底层代码时 , 到底有什么相同之处和不同之处呢? 有这些疑惑的可以自行查询资料 , 或者看看下面列出的这些相关的资料 , 记住 , 请保持各种疑惑和质疑 , 并且学会查询资料揭开疑惑的面纱 , 并找到自己有质疑的相关蛛丝马迹 , 这样 , 我们就在疑惑和质疑中成长了 !
解惑1的参考资料:
http持久连接-keep alive和persistent
HTTP keep-alive和TCP keepalive的区别,你了解吗?
细说Http中的Keep-Alive和Java Http中的Keep-Alive机制
3.2. (新增)流水线(Pipelining)处理
我们还是先看看HTTP1.1标准协议RFC2616中对流水线(Pipelining)的描述吧 .
其实"流水线化"就是把多个HTTP请求放在同一个TCP连接中 , 1个又1个的数据包的直接发送请求 , 而在发送每个请求的过程中 , 不需要等待服务器对前一个请求的响应 , 只不过 , 发起请求的客户端 , 还是要按照发送请求的顺序来接收响应 , 服务器也要按照收到请求的顺序响应请求 , 如果前一个请求非常耗时 , 那么后面的请求就会阻塞 , 也就是"队头阻塞 Head of line blocking" , 这个问题在HTTP1.1协议的层面上 , 并没有完美的解决方案 , 并且大部分浏览器都选择的默认关闭HTTP pipelining这一功能 , 这个问题在2015年HTTP2.0出来后才完美的解决.
并且HTTP1.1的"流水线化"只有GET和HEAD可以进行流水线化 , 而POST则有所限制 , 此外 , 初次建立连接时不应启动流水线化 , 因为对方服务器不一定支持HTTP1.1流水线化.
3.3.HOST域
下面附上了我在HTTP1.1标准协议RFC2616中找到对HOST头域的相关描述的部分截图 , 在HTTP1.0中认为"IP地址"和"服务器"是"一对一"的关系 , 这句话我们通俗的解读一下 , 比如我们现在的HTTP1.1 , 增加了HOST请求资源域后 , 一台Web服务器上的80端口 , 通过不同的域名就能访问到不同的资源 , HTTP1.0它这个一对一的关系是说 , 服务器的IP地址和资源是一对一的关系 , 我们多个域名解析到同一个IP地址上 , 访问域名时 , 其实也是访问的同一个IP地址 , 当然也是访问的同一个资源 , 并且HTTP1.1的请求消息和响应消息都应支持HOST头域 , 请求消息中如果没有HOST头域会报告一个错误(400 Bad Request) , 此外服务器应该接受以绝对路径标记的资源请求.
3.4.dete/timestamp(日期/时间戳)
服务器 , 无论是HTTP1.0还是HTTP1.1 , 都要能解析下面三种dete/time stamp :
Sun, 06 Nov 1994 08:49:37GMT ; RFC 822, updated by RFC 1123
Sunday, 06-Nov-94 08:49:37GMT ; RFC 850, obsoleted by RFC 1036
Sun Nov 6 08:49:371994 ; ANSI C's asctime() format
客户端 , HTTP1.0要求不能生成第3种asctime格式 , HTTP1.1则要求只能生成第1种格式
3.5.Transfer Coding
HTTP1.1支持chunked transfer , 所以有Transfer-Encodeing头域 , HTTP1.0则没有 .
HTTP消息中可以包含任意长度的实体 , 通常它们使用Content-Length来给出消息的结束标志 . 但是 , 对于很多动态生成的响应 , 只能通过缓冲完整的消息来判断消息的大小 , 但是这样做会加大延迟 , 如果不使用长链接 , 还可以使用连接关闭的信号来判断消息的结束标志 .
HTTP1.1中引入了chunked transfer来解决上面的问题 , 发送方将消息分割成若干个任意大小的数据块 (这里的任意大小是指在不是我们认为的任意大小 , 它的最大长度收到数据传输的底层限制 , TCP连接能够传输的每个数据包的最大长度好像是1460个字节 , 有兴趣的可以自行查询相关资料) ,每个数据块在发送是都会附上数据块的长度 , 最后用一个0长度的数据块作为消息的结束标志 . 这种方法允许发送方只缓存消息的一个片段 , 避免缓冲整个消息带来的压力.
HTTP1.0中使用Content-MD5头域 , 发送方要计算这个头域的值 , 就必须要缓冲整个消息才能进行 , 而HTTP1.1采用chunked transfer则可以分块传递消息 , 在最后一个0长度的数据块传递结束后会再传递一个拖尾(trailer) , 并且发送方会在消息中包含一个Trailer头域告诉接收方这个拖尾的存在 , 这个拖尾包含一个或多个头域 , 这些头域是发送方在传递完所有数据块之后再计算出来的值 . 大大的降低了HTTP的延迟 .
3.6.质量值(Quality Values)
这个是属于HTTP中的内容协商相关的一些东西了 , 通过像Accept消息头来使用Quality Values值 , Quality Values值越高 , 权重越大 , 不指定Quality Values值时则默认为1 , 1就是最高权重了 , 一般很少用这个 , 也很少见到用这个 , 我到目前2021年了 , 还真没有见到过这个大面积的使用
相关资料:
Quality values
3.7.缓存处理(Etag , If-Unmodified-Since, If-Match, If-None-Match等)
在HTTP1.0中主要使用If-Modified-Since , Ecpires来做为缓存判断标准 , HTTP1.1则引入了更多的缓存控制策略 , 如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等 , 可供选择的头域来控制缓存策略 (这几个我都加了链接 , 都是解释这些缓存策略的 , 有兴趣的可以点进去看看)
3.8.节约优化
HTTP1.1支持传送内容的一部分 , 在请求头中使用range头域 , 它允许只请求资源的某个部分 . 在响应消息中Content-Range头域声明了返回的资源对象的偏移值和长度 . 如果服务器相应的返回了请求范围的内容 , 则响应状态码为206 , 它可以让请求方知道这不是一个完整的资源对象 . HTTP1.0则不支持上述部分 , 最显著的就是下载大文件的端点续传 , HTTP1.0则不能完成 .
还有就是压缩要传送的数据 , Content-Encodeing头域对消息进行端到端(end-to-end)的编码 , 它可能是资源的固有格式 , 比如png , jpeg等等压缩格式 , 也有可能是其他某种压缩格式 , 比如content-encoding:gzip , 我们在传输字符串文本内容时 , 这个压缩就大有可为了 ; Accept-Encodeing头域是请求方告诉服务器客户端能够解码的编码格式 .
HTTP1.1中加入了一个新的状态码 100 , 这个是节约宽带的 , 比如我们的某个请求包含了一个大的实体数据 , 但是不确定服务器是否能够接收该请求 , 此时若贸然的发出请求 , 如果服务器不能接收该请求 , 这个大实体数据也在请求中传递过去了 , 是要消耗网络资源的 .
这个新的状态码 100 就有用了 , 发送方可以先发送一个只带请求头域的请求 , 如果服务器拒绝这个请求 , 就响应状态码401 , 如果服务器接收请求 , 就响应状态码100 , 发送方就可以继续发送带实体数据的完整请求了 .
3.9.新增状态码(错误通知)
HTTP1.1新增了24个错误状态码 : 100,101,203,205,206,302,303,305,307,405,406,407,408,409,410,411,412,413,414,415,416,417,504,505
我们这里取抄各状态码的中文描述就没意思了 , 所以这里贴一个HTTP状态码对照表的链接 , 有兴趣的可以看看
四.HTTP/1.1 与 2.0 的差别
HTTP2.0是HTTP1.1自1999年发布后的首个更新 , 主要是基于SPDY协议升级更新 , SPDY协议是Goolge开发的基于TCP的应用层协议 , 用以最小化网络延迟 , 提升网络速度 , 优化用户的网络使用体验而开发的 . 所以 , 相对比HTTP1.1 , 在语义完全兼容的基础上 , 大幅度的提升了Web性能 . 这当然是我们最喜闻乐见的 .
既然语义完全兼容 , 我们就来看看对比1.1版本 , 2.0引入了什么新的技术来提升性能的 :
4.1.多路复用 (Multiplexing)
HTTP2.0使用多路复用技术 , 允许单一TCP连接同时处理发送多个请求和同时处理响应多个请求 , 而且并发请求的数量比HTTP1.1大了好几个数量级 . HTTP1.1也可以建立多个TCP连接来并发处理请求 , 但是我们都知道 , 创建TCP连接本身也是有开销的 , 并且浏览器客户端在同一时间 , 针对同一域名下的请求有一定数量限制 . 超过限制数量的请求会被阻塞 .
这里有一张截图 , 当然 , 这是翻译后的截图 , 你如果有兴趣 , 可以点击这段文字查看原文 , 该截图总结了不同浏览器对该限制的数量 . 这也是为何一些站点会有多个静态资源CDN域名的原因之一 , 目的就是变相的解决浏览器针对同一域名的并发请求限制阻塞的问题 .
但HTTP2.0的多路复用则允许同时通过单一HTTP2建立的TCP连接发起多重的请求和响应 , 因此可以很容易的去实现多数据流并行 , 而不用依赖建立多个TCP连接 , HTTP2.0把HTTP协议通信的基本单元缩小为一个一个的帧 , 这些帧对应逻辑流中的消息 , 并行在同一个TCP连接上双向交换消息.
通信都在同一个TCP连接上完成 , 并且这个连接可以承受几乎任意数量的双向数据流 .在本文 第二章节.HTTP的基本优化 中就说过 , 影响HTTP请求的主要因素是 宽带 和 延迟 , 宽带在当今完全是性能过剩 , 关键点还是攻克低延迟这个难题 , TCP连接会随着时间进行自我调谐 , 起初会限制连接的最大速度 , 如果数据成功传输 , 会随着时间的推移提高传输速度 , 这个调谐也被称为TCP慢启动 , 由于这种原因 , 让原本就具有突发性和短时性的HTTP连接变的十分低效 .
HTTP2的多路复用通过让所有数据流共用同一个连接 , 可以更有效的使用TCP连接 , 让高宽带真正释放HTTP的使用体验 .
HTTP2.0在不改动HTTP1.1的语义,方法,状态码等等的情况下 , 使用多路复用技术 , 真正优化了高并发 , 低延迟 , 并且算是暴力解决了HTTP1.1的队头阻塞 , 多TCP连接做并发请求的请求数限制等等诸多造成延迟和阻塞的问题
4.2.二进制分帧层
谈到二进制分帧层 , 我们就必须要知道什么是 "帧" , 这里的 "帧" 它当然不是电影动画的那个 "帧" , 在影像动画中"帧"是最小单位 , "帧"代表了单幅静止不动的影像画面 .
那么言归正传 , 说回我们的二进制分帧层 , 在网络通讯的数据传输中 , 数据在网络上是以很小的单位"帧"来传输的 , "帧"的传输在网络通讯协议中 , 是属于"物理层"了 , 最底层的概率了 , 帧由3部分组成 , 帧头,数据部分,帧尾 , 不同的部分有不同的作用 . 其中,帧头和帧尾包含一些必要的控制信息,比如同步信息、地址信息、差错控制信息等;数据部分则包含网络层传下来的数据,比如IP数据包,等等。 "帧"通过特定的称为网络驱动程序的软件进行成型 , 然后通过网卡发送到网线上 , 通过网线到达目的机器 , 接收端机器的以太网卡捕获到这些帧 , 并告诉操作系统 , 然后对其进行存储 .
在二进制分帧层中 , HTTP2.0会将所有要传输的消息分隔成更小的消息和帧 , 并对他们采用二进制格式的编码 , 其中HTTP1.1的头部信息会被封装到帧头 , 请求实体数据则封装在帧的数据部分 . 二进制分帧层算是与多路复用技术相辅相成 , 更完美的提升了数据高效传输的体验 .
4.3头部压缩
在HTTP1.1中 , 请求和响应都是由状态行 , 请求.响应头部 , 消息体3部分组成的 . 一般来说 , 消息体会经过gzip压缩 , 或者本身就是传输的路 png , jpeg这种压缩过后的二进制文件 , 但状态行和头部却没有经过任何压缩 , 直接以纯文本传输 . 我们现在可以随便找个大型网站看他们的首页是多复杂 , 多少个HTTP请求 , 并且随着WEB功能越来越复杂 , HTTP请求还会越来越多 , 消耗在请求/响应头部的流量越来越多 , 比如每次都要传输UserAgent、Cookie这类不会频繁变化的内容 , 完全是一种浪费 . HTTP1.1是不支持任何方式进行Header数据压缩的 .
HTTP2.0优化头部数据压缩 , 使用HPACK算法 , 对头部的数据进行压缩 , 这样数据体积小了 , 传输也就更快了 .
而且这个数据压缩算法有点牛逼哦 , 比如我们第1次请求 , 头部的Cookie字段和值可能被压缩成36个字符长度 , 但是第2次请求时 , 头部的Cookie字段和值就被压缩成1个字符长度了 , 真正的头部的Cookie字段和值的内容 , 由请求方和服务器双方各自维护着 . 相当于大家每次请求和响应的头部数据 , 双方都缓存了一份 , 给每个缓存的字段和值起了一个代号 , 每次请求和响应的头部中 , 只要传递这个代号就可以 , 真实的头部信息从双方的缓存中去取 , 头部字段内容发生变化时 , 增量传递变化的数据就行了 , 没有变化的字段数据还是传递双方约定的代号 . 这个压缩算法是真牛逼 .
4.4.服务器推送
服务器是一种在客户端请求之前发送数据的机制 . 网页中使用了许多资源 ,如HTML , css , js , 图片 , 字体文件等等 , 在HTTP1.1中这些资源每一个都必须单独的发起请求 , 浏览器从获得HTML后解析和评估页面的时候 , 增量的获取这些资源 , 因为服务器必须等待浏览器的每一个资源请求 , 这时的网络经常是空闲和未被充分使用的 .
HTTP2.0引入了服务器推送 , 它允许服务器推送资源给浏览器 , 在浏览器明确的发出请求之前 , 免得客户端再次发送请求到服务器获取资源 , 这样客户端就可以从本地加载这些资源了 , 也就极大的降低了网络请求所带来的阻塞和延迟 .
服务器推送是HTTP2.0里面 , 唯一一个需要开发者自己配置的功能 , 不然你访问网站的某个HTML页面 , HTTP怎么知道要推送些什么资源 , 并且像一些静态资源在首次请求获得后 , 浏览器也会缓存下来 , 这时服务器推送资源不是浪费流量吗 ? 所以这个功能需要开发者自己配置一些规则 .
参考资料 :
网友评论