文章基调
- 不是科普类文章,不是科普 http2 功能的文章
- 记录 http2 中难以理解的点,系作者在学习 http2 时的困惑,已经最终的理解
- 是个人的理解,可能有不严谨的地方,欢迎讨论
文章图片
如何理解 TCP 分帧 与 http2 分帧 的区别
- 假设「传输完整的数据」是「运输一个订单货物」,每「订单中的一个货物」占满「一个货车厢」
- TCP 位于传输层,可以理解为运输的货车
- TCP 中的每一帧都是有序的,按发车时间标记趟次,第一趟次,第二趟次,第三趟次,第 N 趟次
- TCP 可以同时发若干辆车,假设 4 辆车,则一次发车就有,第一趟次,第二趟次,第三趟次,第四趟次
- 每辆车有些提前到达,有些很慢才到,不一定按照发车的顺序到达目的地
- 当其中一个趟次的车回来了,才发下一趟车次。
- 趟次的作用,是为了货品送到目的地的时候可以重新按顺序排列,可以将每趟次的货理解为高达模型的零件(一车只运送一个零件),有顺序,才能识别重新拼装起来
- 将一个订单中的货,分别通过不同的趟次运输,就是「TCP 二进制分帧」,将订单的货(完整的数据)分成每一车(帧)进行运输
- HTTP 处于应用层,一个 http 请求及响应,可以理解为下订单(请求)购买一批货(响应)的过程,(注意是一批,有若干个货物)而这批货并没有货品名称(无法对应这批货对应的是哪个订单,无法将响应与请求关联起来)
- 这时 http 还没有分帧化,粒度是以订单为单位,一个订单就是一个 http
- 将一个 TCP 链接理解为一个运输合同
文章图片
- http0.9,1.0
【问题】每一次订单都签一次运输合同,很麻烦
- 签一次运输合同(三次握手)
- 下订单(请求)
- 生产货物(服务器计算结果)
- 发起运输(TCP 传输内容)
- 收货物(响应)
- 结一次运输合同的账(四次挥手)
- 签一次运输合同(三次握手)
- 下订单(请求)
- 生产货物(服务器计算结果)
- 发起运输(TCP 传输内容)
- 收货物(响应)
- 结一次运输合同的账(四次挥手)
文章图片
- http1.1 keep-alive
【改进】运输合同改成月结,复用 TCP 链接
【问题】工厂无法同时生产多个订单的货物,需要上一个订单收货
- 签一次运输合同(三次握手)
- 下订单(请求)
- 生产货物(服务器计算结果)
- 发起运输(传输内容)
- 收货物(响应)
- 下订单(请求)
- 生产货物(服务器计算结果)
- 发起运输(传输内容)
- 收货物(响应)
- 结一次运输合同的账(四次挥手)
文章图片
- http1.1 pipeline
【改进】可以同时发多个订单了,通过收货顺序,来识别货物对应的是那一次订单的内容
【改进】工厂可以同时生产多个订单的货
【问题】订单存在依赖关系,即使第二次订单的货物生产好了,也得等第一次订单的货物生产好并全部传输完,才能发货。否则会被当做第一个订单的货
- 签一次运输合同(三次握手)
- 下订单(第一个请求)
- 下订单(第二个请求)
- 同时生产第一个批和第二批货物(服务器计算结果)
- 按顺序发第一个订单的运输(传输内容)
- 按顺序收货物(响应,对应第一个响应)
- 按顺序发第二个订单的运输(传输内容)
- 收货物(响应,对应第二个响应)
- 结一次运输合同的账(四次挥手)
文章图片
- http2
【改进】将一个订单的货拆分成多个批次,为每个批次标识上是哪个订单的货
【改进】由于能识别一批次货品所属订单,最小粒度从一个订单的货,改为一批次的。原本按订单运输,现在改为按批次运输,这个最小颗粒度的变化就是 http2 分帧:将一个响应或请求拆分成多个分帧片段
【改进】最小粒度变成了批次,生产完一批次的货品,就可以马上传输,不需要等整个订单的货全部生产完才传输
- 签一次运输合同(三次握手)
- 下订单(第一个请求)
- 下订单(第二个请求)
- 同时生产第一个订单和第二订单的货物(服务器计算)
- 每生产批次货物,就给这批次的货打上标签,标识是哪个订单的货(分帧)
- 准备好了一批次货物,就发运输,不需要管是哪个订单的(传输)
- 收货,重新分拣是哪个订单的货(根据分帧标识对应是那一次请求的响应)
- 每生产批次货物,就给这批次的货打上标签,标识是哪个订单的货(分帧)
- 准备好了一批次货物,就发运输,不需要管是哪个订单的(传输)
- 收货,重新分拣是哪个订单的货(根据分帧标识对应是那一次请求的响应)
- 直到所有货物都传输完成
- 结一次运输合同的账(四次挥手)
- 总结
- 可以看出,tcp 的分帧与 http2 的分帧是不同维度的区分
- tcp 的分帧维度是一个趟次运输(一个趟次运输一个货物),http2 的分帧维度是 一批货物(可能是一个货物,也可能是若干个货物)
- http2 分帧的本质是将原本一个订单(一个请求或响应),拆分成多个批次(多个帧),缩小数据颗粒度,增加灵活性
- 参考资料
- https://segmentfault.com/q/1010000005167289
- https://blog.csdn.net/u598975767/article/details/112788129
- https://blog.wangriyu.wang/2018/05-HTTP2.html
文章图片
- 本质上,只要给一个订单的货(响应)打上订单(请求)标识,就可以标识是哪个订单的,就可以解决先订单依赖的问题(后一个订单不需要等前一个订单传输完),为什么需要将订单拆散为批次呢?
- 车的数量(TCP 通道宽度,流量宽度)是有限的,拆散了并不是加快运输过程
- 拆成批次(分帧)是为了从根源支持数据的分配传输,如果一个订单的量很大,按订单发货,就得等整个订单的货生产完成才能运输。而拆成批次(分帧)就可以将粒度减低,生产一个批次就运输一个批次,不需要等整个订单的货生产完成。
文章图片
- stackoverflow 中对应的讨论,对应国内论坛
- 文本协议,信息传输过程经历以下步骤
- 编写文本,由于规则比较松散,可能存在多余的前后空格,多余的换行等情况
- 传输文本,传输的是 ASCII,即文字对应的编码
- 读取文本,理解文本,识别 ASCII 码组合起来的单词,再通过字符串匹配的方式匹配意义
- 二进制协议
- 这里的二进制,主要体现在两个方面「二进制帧封装」及「头部压缩」
- 「二进制帧封装」即将数据打散,外包一层二进制帧数据(用于标识当前帧的特性)
- 所以是这一分帧层进行了二进制封装,而不是 http 的内容二进制,所以更合适的称呼应该是「增加了二进制分帧层」
- 这里的二进制帧数据,是用二进制为颗粒度代表数据的含义,每个 0 和 1 代表特殊的含义
- 而文本协议,是通过 ASCII 对应的单词来表达含义,
- 即代表数据的颗粒度不一样了,原本是有若干个字母,现在是由若干个比特
- 【记录 http2 四个难以理解的疑惑点】「头部压缩」
- 通过静态表和动态表的索引值来代表含义
- 相当于双方建立的临时暗号
文章图片
http2 头部压缩,如何做到浏览器动态表与服务器动态表的同步
- 【疑问】由于竞速问题,在请求头未注册到服务器之前接收了另一个请求。
文章图片
- 【方案】http2 在分帧中有明确规则,请求头与响应头的分帧,中间不能插入其他分帧。从而保证完整的请求头和响应头是按顺序传输的。故不会存在竞速问题。
文章图片
- stackoverflow 中对应的讨论
- 在 HTTP2 中的 CONTINUATION 帧有相关的介绍,对应中文文档
-
文章图片
- segmentfault 中关于 CONTINUATION 帧的介绍
文章图片