记录 http2 四个难以理解的疑惑点

文章基调

  • 不是科普类文章,不是科普 http2 功能的文章
  • 记录 http2 中难以理解的点,系作者在学习 http2 时的困惑,已经最终的理解
  • 是个人的理解,可能有不严谨的地方,欢迎讨论
    记录 http2 四个难以理解的疑惑点
    文章图片

    如何理解 TCP 分帧 与 http2 分帧 的区别
  • 假设「传输完整的数据」是「运输一个订单货物」,每「订单中的一个货物」占满「一个货车厢」
  • TCP 位于传输层,可以理解为运输的货车
    • TCP 中的每一帧都是有序的,按发车时间标记趟次,第一趟次,第二趟次,第三趟次,第 N 趟次
    • TCP 可以同时发若干辆车,假设 4 辆车,则一次发车就有,第一趟次,第二趟次,第三趟次,第四趟次
    • 每辆车有些提前到达,有些很慢才到,不一定按照发车的顺序到达目的地
    • 当其中一个趟次的车回来了,才发下一趟车次。
    • 趟次的作用,是为了货品送到目的地的时候可以重新按顺序排列,可以将每趟次的货理解为高达模型的零件(一车只运送一个零件),有顺序,才能识别重新拼装起来
    • 将一个订单中的货,分别通过不同的趟次运输,就是「TCP 二进制分帧」,将订单的货(完整的数据)分成每一车(帧)进行运输
  • HTTP 处于应用层,一个 http 请求及响应,可以理解为下订单(请求)购买一批货(响应)的过程,(注意是一批,有若干个货物)而这批货并没有货品名称(无法对应这批货对应的是哪个订单,无法将响应与请求关联起来)
    • 这时 http 还没有分帧化,粒度是以订单为单位,一个订单就是一个 http
  • 将一个 TCP 链接理解为一个运输合同
记录 http2 四个难以理解的疑惑点
文章图片

  • http0.9,1.0
    【问题】每一次订单都签一次运输合同,很麻烦
    • 签一次运输合同(三次握手)
    • 下订单(请求)
    • 生产货物(服务器计算结果)
    • 发起运输(TCP 传输内容)
    • 收货物(响应)
    • 结一次运输合同的账(四次挥手)
    • 签一次运输合同(三次握手)
    • 下订单(请求)
    • 生产货物(服务器计算结果)
    • 发起运输(TCP 传输内容)
    • 收货物(响应)
    • 结一次运输合同的账(四次挥手)
      记录 http2 四个难以理解的疑惑点
      文章图片
  • http1.1 keep-alive
    【改进】运输合同改成月结,复用 TCP 链接
    【问题】工厂无法同时生产多个订单的货物,需要上一个订单收货
    • 签一次运输合同(三次握手)
    • 下订单(请求)
    • 生产货物(服务器计算结果)
    • 发起运输(传输内容)
    • 收货物(响应)
    • 下订单(请求)
    • 生产货物(服务器计算结果)
    • 发起运输(传输内容)
    • 收货物(响应)
    • 结一次运输合同的账(四次挥手)
      记录 http2 四个难以理解的疑惑点
      文章图片
  • http1.1 pipeline
    【改进】可以同时发多个订单了,通过收货顺序,来识别货物对应的是那一次订单的内容
    【改进】工厂可以同时生产多个订单的货
    【问题】订单存在依赖关系,即使第二次订单的货物生产好了,也得等第一次订单的货物生产好并全部传输完,才能发货。否则会被当做第一个订单的货
    • 签一次运输合同(三次握手)
    • 下订单(第一个请求)
    • 下订单(第二个请求)
    • 同时生产第一个批和第二批货物(服务器计算结果)
    • 按顺序发第一个订单的运输(传输内容)
    • 按顺序收货物(响应,对应第一个响应)
    • 按顺序发第二个订单的运输(传输内容)
    • 收货物(响应,对应第二个响应)
    • 结一次运输合同的账(四次挥手)
      记录 http2 四个难以理解的疑惑点
      文章图片
  • 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
    记录 http2 四个难以理解的疑惑点
    文章图片

为什么要分帧
  • 本质上,只要给一个订单的货(响应)打上订单(请求)标识,就可以标识是哪个订单的,就可以解决先订单依赖的问题(后一个订单不需要等前一个订单传输完),为什么需要将订单拆散为批次呢?
    • 车的数量(TCP 通道宽度,流量宽度)是有限的,拆散了并不是加快运输过程
  • 拆成批次(分帧)是为了从根源支持数据的分配传输,如果一个订单的量很大,按订单发货,就得等整个订单的货生产完成才能运输。而拆成批次(分帧)就可以将粒度减低,生产一个批次就运输一个批次,不需要等整个订单的货生产完成。
    记录 http2 四个难以理解的疑惑点
    文章图片
如何理解 http1.1 是文本协议,http2 是二进制协议
  • stackoverflow 中对应的讨论,对应国内论坛
  • 文本协议,信息传输过程经历以下步骤
    • 编写文本,由于规则比较松散,可能存在多余的前后空格,多余的换行等情况
    • 传输文本,传输的是 ASCII,即文字对应的编码
    • 读取文本,理解文本,识别 ASCII 码组合起来的单词,再通过字符串匹配的方式匹配意义
  • 二进制协议
    • 这里的二进制,主要体现在两个方面「二进制帧封装」及「头部压缩」
    • 「二进制帧封装」即将数据打散,外包一层二进制帧数据(用于标识当前帧的特性)
      • 所以是这一分帧层进行了二进制封装,而不是 http 的内容二进制,所以更合适的称呼应该是「增加了二进制分帧层」
      • 这里的二进制帧数据,是用二进制为颗粒度代表数据的含义,每个 0 和 1 代表特殊的含义
      • 而文本协议,是通过 ASCII 对应的单词来表达含义,
      • 即代表数据的颗粒度不一样了,原本是有若干个字母,现在是由若干个比特
    • 【记录 http2 四个难以理解的疑惑点】「头部压缩」
      • 通过静态表和动态表的索引值来代表含义
      • 相当于双方建立的临时暗号
记录 http2 四个难以理解的疑惑点
文章图片

http2 头部压缩,如何做到浏览器动态表与服务器动态表的同步
  • 【疑问】由于竞速问题,在请求头未注册到服务器之前接收了另一个请求。
    记录 http2 四个难以理解的疑惑点
    文章图片
  • 【方案】http2 在分帧中有明确规则,请求头与响应头的分帧,中间不能插入其他分帧。从而保证完整的请求头和响应头是按顺序传输的。故不会存在竞速问题。
    记录 http2 四个难以理解的疑惑点
    文章图片
  • stackoverflow 中对应的讨论
  • 在 HTTP2 中的 CONTINUATION 帧有相关的介绍,对应中文文档
  • 记录 http2 四个难以理解的疑惑点
    文章图片
  • segmentfault 中关于 CONTINUATION 帧的介绍
    记录 http2 四个难以理解的疑惑点
    文章图片

    推荐阅读