写点不一样的
这是大白的 第 42 篇 原创文章.
1 前言
前面写了 10 多篇 关于 Redis 底层实现, 工程架构, 实际应用的文章, 感兴趣的读者可以进行阅读, 如有问题欢迎交流:
接下来本号将推出 TCP/IP 协议栈相关的系列文章 ,TCP/IP 协议栈是 Linux 内核中非常重要的部分, 无论怎么写都 不能事无巨细面面俱到 , 因此本号仍然只能挑选一些 热门或小众但有趣的话题 来展开.
今天一起来研究 Http 协议的一些事情, 通过本文你将了解到以下内容:
Http 协议各版本的对比和优缺点
Http2.0 协议相关的 SPDY 协议, 二进制分帧协议, 多路复用, 首部压缩, 服务推送等基本原理
乘风破浪前往知识的海洋吧, 大白船长 要开船了!
2. Http 协议各版本的对比
Http 超文本传输协议同空气一般, 感触不到它的存在但是又无处不在 , 笔者从维基百科摘录了一些 Http 协议的发展历程的简单信息, 一起来看下吧:
超文本传输协议是 分布式协作超媒体信息系统的应用协议 . 超文本传输协议是万维网数据通信的基础, 在万维网中超文本文档包括到用户可以轻松访问的其他资源的超链接.
蒂姆. 伯纳斯. 李于 1989 年在欧洲核子研究中心发起了超文本传输协议的开发. 早期的超文本传输协议征求意见 (RFCs) 的开发是由互联网工程任务组 (IETF) 和万维网联盟 (W3C) 共同努力的结果, 其工作后来转移到 IETF.
万维网之父蒂姆. 伯纳斯. 李简介
Tim Berners-Lee 是英国工程师和计算机科学家, 最著名的是万维网的发明者. 他是 牛津大学计算机科学教授和麻省理工学院教授 .
他于 1989 年 3 月 12 日提出了一种信息管理系统, 然后在同年 11 月中旬通过 Internet 实现了超文本传输协议 HTTP 客户端和服务器之间的 首次成功通信 .
他是万维网联盟 W3C 的负责人, 该联盟负责监督 web 的持续发展, 他还是 万维网基金会的创始人 , 还是麻省理工学院计算机科学和人工智能实验室 CSAIL 的 3Com 创始人主席和高级研究员, 他也是网络科学研究计划 WSRI 的主任和 MIT 集体智慧中心的顾问委员会成员, 他也是开放数据研究所的 创始人兼总裁 , 目前是社交网络 MeWe 的顾问.
2004 年, 伯纳斯. 李因其开创性工作而被女王伊丽莎白二世 封为爵士 . 在 2009 年 4 月, 他当选为 美国国家科学院外籍研究员 , 位列《时代》杂志的 20 世纪 100 位最重要人物 名单被誉为 "万维网发明者" 获得了 2016 年 图灵奖 .
http 各个版本的基本情况
http 协议经过 20 多年的演进出现过 0.9,1.0,1.1,2.0,3.0 五个主要版本, 笔者画了张图看下:
A.Http0.9 版本
0.9 是鼻祖版本, 它的主要特点包括:
请求方法支持有限
只支持 GET 请求方式, 不支持其他请求方式 因此客户端向服务端传输信息的量非常有限, 也就是现在常用的 Post 请求无法使用
不支持请求头 header
不能在请求中指定版本号, 服务端只具有返回 html 字符串的能力
响应即关闭
服务端相响应之后, 立即关闭 TCP 连接
B.Http1.0 版本
1.0 版本主要是对 0.9 版本的强化, 效果也比较明显, 主要特性和缺点包括:
丰富请求方法
请求方式新增了 POST,DELETE,PUT,HEADER 等方式, 提高了客户端向服务端发送信息的量级
增加请求头和响应头
增添了请求头和响应头的概念, 可以在通信中指定了 HTTP 协议版本号, 以及其他 header 信息, 使得 C/S 交互更加灵活方便
丰富数据传输内容
扩充了传输内容格式包括: 图片, 音视频资源, 二进制 等都可以进行传输, 相比 0.9 的只能传输 HTML 内容让 http 的应用场景更多
链接复用性差
1.0 版本中每个 TCP 连接只能发送一个请求, 数据发送完毕连接就关闭, 如果还要请求其他资源, 就必须重新建立连接. TCP 为了保证正确性和可靠性需要客户端和服务器三次握手和四次挥手, 因此建立连接成本很高, 基于拥塞控制开始时发送速率较慢, 所以 1.0 版本的 性能并不理想 .
无状态无连接的弊端
1.0 版本是 无状态且无连接 的, 换句话说就是服务器不跟踪不记录请求过的状态, 客户端每次请求都需要建立 tcp 连接不能复用, 并且 1.0 规定在前一个请求响应到达之后下一个请求才能发送, 如果前一个阻塞后面的请求就会被阻塞. 丢包和乱序问题 和高成本的链接过程让复用和 队头阻塞 产生很多问题, 所以 无连接无状态 是 1.0 版本的一个 弱肋 .
C.Http1.1 版本
1.1 版本在 1.0 版本发布后大约 1 年就推出了, 是 对 1.0 版本的优化和完善 ,1.1 版本的主要特点包括:
增加长连接
新增 Connection 字段, 可以设置 keep-alive 值保持连接不断开, 即 TCP 连接默认不关闭, 可以被多个请求复用, 这也是 1.1 版本很重要的优化, 但是在 S 端服务器只有处理完一个回应, 才会进行下一个回应. 要是前面的回应特别慢, 后面就会有许多请求排队等着, 仍然存在队头阻塞问题.
管道化
在长连接的基础上, 管道化可以不等第一个请求响应继续发送后面的请求, 但响应的顺序还是按照请求的顺序返回, 即在同一个 TCP 连接中, 客户端可以同时发送多个请求, 进一步改进了 HTTP 协议的传输效率.
更多的请求方法
增加了 PUT,PATCH,OPTIONS,DELETE 等请求方式.
host 字段
Host 字段用来指定服务器的域名, 这样就可以将多种请求发往同一台服务器上的不同网站, 提高了机器的复用, 这个也是重要的优化
D.Http2.0 版本
2.0 版本是个里程碑式的版本, 相比 1.x 版本有了非常多的优化去适应当前的网络场景, 其中几个重要功能点包括:
二进制格式
1.x 是文本协议, 然而 2.0 是以二进制帧为基本单位, 可以说是一个二进制协议, 将所有传输的信息分割为消息和帧, 并采用二进制格式的编码, 一帧中包含数据和标识符, 使得网络传输变得高效而灵活.
多路复用
这是一个非常重要的改进, 1.x 中建立多个连接的消耗以及效率都存在问题, 2.0 版本的多路复用多个请求共用一个连接, 多个请求可以同时在一个 TCP 连接上并发, 主要借助于二进制帧中的标识进行区分实现链路的复用.
头部压缩
2.0 版本使用使用 HPACK 算法对头部 header 数据进行压缩, 从而减少请求的大小提高效率, 这个非常好理解, 之前每次发送都要带相同的 header, 显得很冗余, 2.0 版本对头部信息进行增量更新有效减少了头部数据的传输.
服务端推送
这个功能有点意思, 之前 1.x 版本服务端都是收到请求后被动执行, 在 2.0 版本允许服务器主动向客户端发送资源, 这样在客户端可以起到加速的作用.
3 Http2.0 详解
前面对比了几个版本的演进和优化过程, 接下来深入研究下 2.0 版本的一些特性及其基本实现原理.
从对比来看 2.0 版本并不是在 1.1 版本上的一些 优化而是革新 , 因为 2.0 背负了更多的 性能目标任务 ,1.1 虽然增加了长连接和管道化, 但是从根本上并没有实现真正的高性能.
2.0 的设计目标是在 兼容 1.x 语义和操作 的基础上, 给用户带来 更快捷, 更简单, 更安全 的体验高效地利用当前的网络带宽, 为此 2.0 做了很多调整主要包括: 二进制化分帧, 多路复用, 头部压缩 等.
akamai 做了 http2.0 和 http1.1 在加载过程中的对比效果( 实验中加载 379 个小片段 在笔者的电脑上的加载时间是 0.99s VS 5.80s ):
https://http2.akamai.com/demo
3.1 SPDY 协议
要说 2.0 版本标准和新特性就必须提谷歌的 SPDY 协议 , 看一下百度百科:
SPDY 是 Google 开发的基于 TCP 的会话层协议, 用以最小化网络延迟, 提升网络速度, 优化用户的网络使用体验. SPDY 并不是一种用于替代 HTTP 的协议, 而是对 HTTP 协议的增强.
新协议的功能包括 数据流的多路复用, 请求优先级以及 HTTP 报头压缩 . 谷歌表示引入 SPDY 协议后, 在实验室测试中页面加载速度比原先快 64%.
随后 SPDY 协议得到 Chrome,Firefox 等大型浏览器的支持, 在一些大型网站和小型网站种部署, 这个高效的协议引起了 HTTP 工作组 的注意, 在 此基础上制定了官方 Http2.0 标准 .
之后几年 SPDY 和 Http2.0 继续演进相互促进, Http2.0 让服务器, 浏览器和网站开发者在新协议中获得更好的体验, 很快被大众所认可.
3.2 二进制分帧层
二进制分帧层 binary framing layer 在不修改请求方法和语义的基础上, 重新设计了 编码机制 , 如图为 http2.0 分层结构( 图片来自参考 4 ):
二进制编码机制使得通信可以在 单个 TCP 连接 上进行, 该连接在整个对话期间一直处于活跃状态.
二进制协议将通信 数据分解为更小的帧 , 数据帧充斥在 C/S 之间的双向数据流中, 就像双向多车道的高速路, 来往如织川流不息:
要理解二进制分帧层需要知道四个概念:
链接 Link
就是指一条 C/S 之间的 TCP 链接, 这是个基础的链路数据的高速公路
数据流 Stream
已建立的 TCP 连接内的双向字节流, TCP 链接中可以承载一条或多条消息
消息 Message
消息属于一个数据流, 消息就是逻辑请求或响应消息对应的完整的一系列帧, 也就是帧组成了消息
帧 Frame
帧是通信的最小单位, 每个帧都包含帧头和消息体, 标识出当前帧所属的数据流
四者是 一对多的 包含 关系, 笔者画了一张图:
再来看一下 HeadersFrame 头部帧的结构:
再来看一下 HeadersFrame 头部帧的结构: 从各个域可以看到长度, 类型, 标志位, 流标识符, 数据净荷等, 感兴趣可以阅读 rfc7540 相关文档.
https://httpwg.org/specs/rfc7540.html
总之 2.0 版本将通信数据分解为二进制编码帧进行交换, 每个帧对应着特定数据流中的特定消息, 所有帧和流都在一个 TCP 连接内复用, 二进制分帧协议是 2.0 其他功能和性能优化的重要基础.
3.3 多路复用
1.1 版本中存在 队首阻塞问题 , 因此如果客户端要发起多个并行请求来提升性能, 必须使用 多个 TCP 连接 , 这样就要承受 更大延时和建链拆链成本 , 不能有效利用 TCP 链接.
由于 2.0 版本中使用新的二进制分帧协议突破了 1.0 的诸多限制, 从根本上实现了真正的 请求和响应多路复用 .
客户端和服务器将交互数据分解为 相互独立的帧 , 互不影响地 交错传输 , 最后再在对端根据 帧头中的流标识符 把它们 重新组装 起来, 从而实现了 TCP 链接的多路复用.
如图展示了 2.0 版本的基于帧的消息通信过程 ( 图片来自参考 4 ) :
3.4 首部压缩
A.Header 冗余传输
我们都知道 http 请求都有 header 部分, 每个包都有并且相对于一条链接而言 大部分的包的 header 部分都是相同 的, 这样的话每次传输相同的部分确实 非常浪费 .
现代网络中每个网页平均包含 100 多个 http 请求, 每个请求头平均有 300-500 字节, 总数据量达到几十 KB 以上, 这样可能造成数据延时, 尤其复杂 的 Wi-Fi 环境或者蜂窝网络 , 这样只能看到手机在转圈, 但是这些请求头之间通常几乎没有变化, 在本已经拥挤的链路中多次传输相同的数据部分确实不是高效做法.
基于 TCP 设计的 拥塞控制 具有 线增积减 AIMD 特性 , 如果发生丢包那么传输速率将大幅度下降, 这样在拥挤的网络环境中大的包头意味着只能 加剧拥塞控制造成的低速率传输 .
B.Http 压缩和犯罪攻击
在 2.0 版本的 HPACK 算法之前, http 压缩使用 gzip 去压缩, 后来提出的 SPDY 算法对 Headers 进行特殊设计, 但是它依旧使用的是 DEFLATE 算法 .
在后面的一些实际应用中发现 DEFLATE 和 SPDY 都有被攻击的危险 , 因为 DEFLATE 算法使用后向 字符串匹配和动态 Huffman 编码 , 攻击者可以控制部分请求头部通过修改请求部分然后看压缩之后大小改变多少, 如果变小了攻击者就知道注入的文本和请求中的某些内容有重复.
这个过程有点像 俄罗斯方块的消除过程 , 这样经过一段时间的尝试数据内容就可能被全部搞清楚, 由于这种风险的存在才研发出更安全的压缩算法.
C.HPACK 算法
2.0 版本中 HPACK 算法在 C/S 中使用 首部表 来存储之前发送的键值对, 对于相同的数据通信期间几乎不会改变的通用键值对只需发送一次即可.
极端情况如果请求头每次没有变化, 那么传输中则不包含首部, 也就是首部开销就是 零字节 . 如果首部键值对发生变化了, 也只需要发送变化的数据, 并且将 新增或修改的首部帧会被追加到首部表 , 首部表在链接存活期始终存在, 并且由客户端和服务器 共同更新和维护 .
简单说就是客户端和服务端共同维护了一个 key-value 的结构, 发生变化时则更新传输, 否则就不传输, 这样相当于 首次全量传输之后增量更新传输 即可, 这个思想在日常开发中也非常普遍, 不用想的太复杂.
如图展示了首部表的更新过程 ( 图片来自参考 4 ) :
hpack 算法的相关文档:
https://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12
3.5 服务端推送
服务端推送是 2.0 版本新增的一个强大功能, 和一般的 一问一答 式的 C/S 交互不同, 推送式交互中服务器可以对客户端的一个请求发送多个响应 , 除了对最初请求的响应外还向客户端推送额外资源, 无需客户端明确地请求也可以推送.
举个栗子:
想象一下你去餐厅吃饭, 服务好的快餐厅在你点好一份牛肉面之后, 还会给你送上餐巾纸, 筷子, 勺子甚至调料等, 这样主动式的服务, 节约了客人的时间并且提高了用餐体验.
在实际的 C/S 交互中这种 主动推送额外资源 的方法很有效, 因为几乎每个网络应用都会包含多种资源, 客户端需要全部逐个获取它们, 此时如果让服务器提前推送这些资源, 从而可以 有效减少额外的延迟时 间 , 因为服务器可以知道客户端下一步要请求什么资源.
如图为服务端推送的简单过程 ( 图片来自参考 4 ) :
4. 总结
本文通过介绍 Http 协议的历史演进, 各个版本的主要特征和优缺点, 重点介绍了 Http2.0 协议的一些特性, 包括: SPDY 协议, 二进制分帧协议, 多路复用, 首部压缩, 服务端推送 等重要功能, 篇幅有限不能展开太多 .
虽然 http2.0 版本协议有很多非常优秀的功能并且在 2015 年正式发布 , 现在国内外一些大厂基本都有使用 http2.0 承担部分请求, 但是目前仍 然未广泛普及 .
目前 http3.0 版本在 2018 年也推出来了, 至于 http2.0 和 http3.0 的推广和普及是需要时间的, 但是坚信我们的网络可以 更安全, 更快捷, 更节约 .
本次的旅程就此结束啦, 期待下一次的航行!
5. 巨人的肩膀
- https://juejin.im/post/5d9abde7e51d4578110dc77f
- https://lixiaoyu.cc/2018/09/04/computer-network-12-http-version-differences/
- https://www.ibm.com/developerworks/cn/web/wa-http2-under-the-hood/index.html
- https://developers.google.com/web/fundamentals/performance/http2?hl=zh-cn
- https://hpbn.co/primer-on-web-performance/#latency-as-a-performance-bottleneck
- https://httpwg.org/specs/rfc7540.html
- https://juejin.im/post/5d033d3df265da1baa1e700e
- https://juejin.im/post/5da16e9ef265da5b76373d0e
6. 关于我
来源: http://www.tuicool.com/articles/FNbEB3M