浅谈http2协议

浅谈http2协议

Scroll Down

浅谈http2协议

http/1.x协议有什么问题

http协议(hypertext transfer protocol)是目前运用最成功的应用层协议,几乎所有的浏览器、手机app都使用这种协议进行通讯。目前主流的http协议为http/1.1协议,然而,http/1.x时代的某些特性会对当今的应用程序产生负面的影响,主要体现在以下几点

  1. 在http/1.0时代,每一次http请求都会建立一次tcp连接,建立tcp连接的消耗在高并发的场景下是不能忽略的。然而,在相同的连接四元组(发送方ip port,接收方ip port)的情况下,我们完全没有必要重新建立tcp连接。
    尽管我们可以在http header中加入Keep-Alive复用同一个tcp请求,
    在http/1.1时代,引入了http管道(http pipeline)概念,将浏览器的FIFO队列移动到了服务端,浏览器会将请求全部都发送给服务端,然后等着接受就ok了。这样服务器处理完第一个就处理第二个,不会有空闲等待了。但是,http/1.1不支持连接复用,这个方案有几个很难解决的问题
  • 服务端收到多个管道请求后,需要按接受顺序一个一个处理,如果第一个请求特别慢,后续所有相应都会跟着阻塞。这种情况就是队首阻塞问题(head of line blocking)

  • 服务端为了保证按顺序回传,需要缓存多个相应,从而占用更多服务器资源

  • 如果浏览器连续发送多个请求,中间因为网络导致断开,无法得知服务器处理情况,只能进行全部重试导致服务器重复处理

  1. http/1.x时代的http header请求头总是重复和冗余的,协议头部使用纯文本格式,没有任何压缩,且包含很多冗余信息(例如 Cookie、UserAgent 每次都会携带)这造成了大量没有必要的网络开销,导致tcp的拥塞窗口快速填充,这回导致当在一个新的TCP连接上发出多个请求时,造成过多的延迟.

http2协议

http2协议是构建于http语义之上的,即它支持http/1.x协议的所有核心特性但是对性能上做了大量的优化。这些优化主要有:

  • 二进制分帧
    http2协议最大的变化在于重新定义了格式化和传输数据的方式。这是通过在高层的http-api和tcp连接之间加入二进制分桢层实现的。这样就能使原来的web应用并不用任何的改变,性能却能带来质的改变。

  • 连接复用
    http2支持了连接复用,即同域名下所有通信都在单个tcp连接上完成,这个连接tcp可以承载任意数量的双向数据流。可以支持任意数量的http请求。且互相之间互不影响。不用再因为队首阻塞而导致后续的请求无法处理。

  • 头部压缩(header compression)
    上面提到,http header请求头总是重复和冗余的,且直接以纯文本传输。随着web应用越来越复杂,请求消耗在头部的流量越多,尤其每次都要传输UserAgent、Cookie这种不变的内容。 所以,http2协议使用hpack进行了头部的压缩,以减少传输成本。

  • 服务器推送(server push)
    前面提到,因为http/2是连接复用的,建立的tcp连接可以承载任意数量的双向数据流,这就意味着,服务器可以支持向客户端推送消息。

flow control

http2协议的一些基础概念

  • Frame(帧) 帧是http/2协议的最小通信单位。桢用于承载特性的数据类型,如http头,body等。每个帧都包含帧首部,其中会标识出当前帧存在的流。
  • Message(消息) 消息指的是http/2中逻辑上的http消息,例如请求和相应等,消息由一个或多个帧组成。
  • Stream(流) 流指的是存在于连接的一个虚拟通道,流可以承载双向的消息。每个流都有一个唯一的证书id
  • Connection(连接) 指对应的tcp连接

二进制分帧

HTTP/1 的请求和响应报文,都是由起始行、首部和实体正文(可选)组成,各部分之间以文本换行符分隔。而 HTTP/2 将请求和响应数据分割为更小的帧,并对它们采用二进制编码。下面这幅图中的 Binary Framing 就是新增的二进制分帧层:

imagepng

http2将请求和相应数据分割为更小的帧(frame),并对每一个frame采用二进制编码,具体帧的格式为:

+-----------------------------------------------+
 |                 Length (24)                   |
 +---------------+---------------+---------------+
 |   Type (8)    |   Flags (8)   |
 +-+-------------+---------------+-------------------------------+
 |R|                 Stream Identifier (31)                      |
 +=+=============================================================+
 |                   Frame Payload (0...)                      ...
 +---------------------------------------------------------------+
  • length: length代表了一个frame的长度,是一个24bit的int。

  • type: type代表了frame的类型,用8个bit来表示,一共有以下类型

    • FrameData 0x0
      这个帧中传输的数据是承载http请求和返回的主要载体,如承载http/1.x协议中的body等数据,主要包含data和padding填充

      +---------------+
       |Pad Length? (8)|
       +---------------+-----------------------------------------------+
       | Data (*) ...
       +---------------------------------------------------------------+ 
      | Padding (*) ...
       +---------------------------------------------------------------+
      
    • FrameHeaders 0x1
      这个帧用于传输http header,传输的数据为序列化后的数据被分为一个或多个8位字节序列,成为header block fragment在FrameHeaders帧中传输。FrameHeader帧中还有如e、stream dependency 和 weight是涉及到和包依赖和优先级的内容,借助于FramePriority帧,客户端可以告知服务器当前的流依赖于其他哪个流。该功能让客户端能建立一个优先级“树”,所有“子流”会依赖于“父流”的传输完成情况。

      +---------------+
      |Pad Length? (8)|
      +-+-------------+-----------------------------------------------+
      |E|                 Stream Dependency? (31)                     |
      +-+-------------+-----------------------------------------------+
      |  Weight? (8)  |
      +-+-------------+-----------------------------------------------+
      |                   Header Block Fragment (*)                 ...
      +---------------------------------------------------------------+
      |                           Padding (*)                       ...
      +---------------------------------------------------------------+
      
    • FramePriority 0x2

      FramePriority帧被用于处理帧之间的优先级和依赖关系的内容,后面会简单介绍,这里先贴出数据内容

       +-+-------------------------------------------------------------+
       |E|                  Stream Dependency (31)                     |
       +-+-------------+-----------------------------------------------+
       |   Weight (8)  |
       +-+-------------+
      
    • FrameRSTStream 0x3
      FrameRSTStream会立即终止一个流。发送FrameRSTStream帧一般是请求关闭一个流或者表明发生了异常的情况。
      数据内容 FrameRSTStream使用一个32位的int来传输错误码

      +---------------------------------------------------------------+
      |                        Error Code (32)                        |
      +---------------------------------------------------------------+
      

      常见的错误码大概有一下几种

          ErrCodeNo                 ErrCode = 0x0
          ErrCodeProtocol           ErrCode = 0x1
          ErrCodeInternal           ErrCode = 0x2
          ErrCodeFlowControl        ErrCode = 0x3
          ErrCodeSettingsTimeout    ErrCode = 0x4
          ErrCodeStreamClosed       ErrCode = 0x5
          ErrCodeFrameSize          ErrCode = 0x6
          ErrCodeRefusedStream      ErrCode = 0x7
          ErrCodeCancel             ErrCode = 0x8
          ErrCodeCompression        ErrCode = 0x9
          ErrCodeConnect            ErrCode = 0xa
          ErrCodeEnhanceYourCalm    ErrCode = 0xb
          ErrCodeInadequateSecurity ErrCode = 0xc
          ErrCodeHTTP11Required     ErrCode = 0xd
      
    • FrameSettings 0x4
      FrameSettings用于传输配置参数信息。它描述的是发送方的特征,相同的参数的不同的值在不同的端中可能会不同,例如,客户端可能设置了一个很高的初始滑动窗口(initial flow control window).服务器可能设置了一个较低的值
      FrameSettings帧的数据格式如下,是由一个16位的标识符和32位的值

      +-------------------------------+
      |       Identifier (16)         |
      +-------------------------------+-------------------------------+
      |                        Value (32)                             |
      +---------------------------------------------------------------+
      

      具体的参数有如下几个:

      • SETTINGS_HEADER_TABLE_SIZE 0x1
        用于通知对端最大头部压缩表(header compression table)的大小,主要用于http2头部压缩使用
      • SETTINGS_ENABLE_PUSH 0x2
        用于设置是否开启server_push。如果设置为0,将不能发送FramePushPromise帧
      • SETTINGS_MAX_CONCURRENT_STREAMS 0x3
        用于设置发送方的最大并发流的数量
      • SETTINGS_INITIAL_WINDOW_SIZE 0x4
        用于设置滑动窗口的初始窗口大小
      • SETTINGS_MAX_FRAME_SIZE 0x5
        用于设置发送方能接受的最大帧大小
      • SETTINGS_MAX_HEADER_LIST_SIZE 0x6
        用于设置发送方能够接受的最大头列表(header list),这个值是基于未压缩的头字段的。该参数的初始值是无限的。
    • FramePushPromise 0x5
      PUSH_PROMISE 帧用于在发送方打算初始化的流之前提前通知对方。

    • FramePing 0x6
      FramePing用于检测一个连接(connection)是否还可用,是一种用于测量来自发送方的最小往返时间的机制

    • FrameGoAway 0x7
      FrameGoAway 用于启动一个连接的关闭或发出严重错误条件的信号。FrameGoAway允许一个端优雅地停止接收新的流的同时,完成目前已经建立的流。

    • FrameWindowUpdate 0x8
      WINDOW_UPDATE 用于实现滑动窗口控制,滑动窗口可以在两个维度进行控制,一个独立的流或者一个连接。默认窗口大小为65535个字节,WINDOW_UPDATE用于传输窗口大小的增量

      +-+-------------------------------------------------------------+
      |R|              Window Size Increment (31)                     |
      +-+-------------------------------------------------------------+
      
    • FrameContinuation 0x9
      FrameContinuation用于继续发送一个Header的header block fragment。只要在前的Frame是FrameHeader或FramePushPromise,且FrameContinuation未收到END_HEADERS这个flag,就可以发送任意个FrameContinuation帧。这个帧的发送一般是用于发送长度很大的header,如果超过了单帧能发送的最大限制,就需要拆分出来,第一帧发送FrameHeader,剩下的发送FrameContinuation。

  • flags:

连接复用

前面提到,http2支持在同一tcp连接上的多个双向数据流中发送消息,消息又由多个帧组成,多个帧之间可以乱序发送,可以根据帧首部的流标识重新组装。如下图所示:
imagepng

头部压缩

优先级priority

一个http2请求的例子