netty对http2协议的解析

栏目: 后端 · 前端 · 发布时间: 7年前

内容简介:netty对http2协议的解析

前言(待整理)

本文从源码角度分析netty-codec-http2对http2协议的实现,netty 对 http1.1的实现参见 netty对http协议解析原理(一)

http2协议

http/2中文版 根据rfc7540翻译

HTTP2引入了一下的三个新概念:

  1. Stream: 已经建立连接的双向字节流,用唯一ID标示,可以传输一个或多个消息
  2. Message: 逻辑/语义上的HTTP消息 ,请求或者响应,可以包含多个 frame
  3. Frame:HTTP2通信的最小单位,二进制头封装,封装HTTP头部或body

所以,直观来说,http2通信,就是收发一个个Http2Frame

Http2Frame 格式

bit数 作用
length 24 payload length
type 8 帧的类型
flags 8 比如一个END_STREAM 标志位,表示一个流的结束
R 1 保留字段
stream identifier 31 标明帧所属的stream
payload

Http2Frame 类型

type值
data 0x0
header 0x1
PRIORITY 0x2
RST_STREAM 0x3 流结束帧,用于终止异常流
SETTINGS 0x4 连接配置参数帧 设置帧由两个终端在连接开始时发送,连接生存期的任意时间发送;设置帧的参数将替换参数中现有值;client和server都可以发送;设置帧总是应用于连接,而不是一个单独的流;
PUSH_PROMISE 0x5 推送承诺帧
PRIORITY 0x6 检测连接是否可用
GOAWAY 0x7 通知对端不要在连接上建新流
WINDOW_UPDATE 0x8 实现流量控制
CONTINUATION 0x9

我们可以将frame笼统的分为data frame和控制frame,每一种类型的payload都是有自己的结构

请求过程

http2 的版本标识:

  1. h2:基于TLS之上构建的HTTP/2,作为ALPN的标识符,两个字节表示,0x68, 0x32,即https
  2. h2c:直接在TCP之上构建的HTTP/2,缺乏安全保证,即http

在不知道服务器是否支持http2的情况下,可以利用http的升级机制发送试探包

netty对http2协议的解析

http2连接过程(不同于http1直接发送请求)

netty对http2协议的解析

流量控制

简单说,就是发送方启动是有个窗口大小(默认64K-1),发送了10K的DATA帧,就要在窗口里扣血(减掉10K),如果扣到0或者负数,就不能再发送;接收方收到后,回复WINDOW_UPDATE帧,里面包含一个窗口大小,数据发送方收到这个窗口大小,就回血,如果回血到正数,就又能发不超过窗口大小的DATA帧。

这种流控方式就带来一些问题:

  1. 如果接收方发的WINDOW_UPDATE frame丢了,当然tcp会保证重传,但在WINDOW_UPDATE重传之前,就限制了发送方发送数据
  2. 一旦发送方初始windows size确定,那么发送方的发送速度是由接收方 + 网络传输决定的,如果发送方的速度大于接收方的应答,那么就会有大量的数据pending。

流控只限定data类型的frame,其它限定参见 http2-frame-WINDOW_UPDATE

netty实现

前文提过,http2通信,就是收发一个个Http2Frame。在代码层面上,接口也是围绕各个类型的frame的onXXRead和writeXXX来进行的。

上层接口关系如下

netty对http2协议的解析

主要的点如下

  1. Http2ConnectionHandler extends ByteToMessageDecoder,整个读的流程由ByteToMessageDecoder.onDecode方法驱动。
  2. Http2FrameReader和Http2FrameWriter只是单纯的负责读写frame, Http2FrameReader.readFrame(ChannelHandlerContext ctx, ByteBuf input, Http2FrameListener listener) throws Http2Exception; 中有一个listener成员,读取的frame会根据类型的不同,触发listener onXXRead方法的执行。Http2FrameWriter负责各类frame的写入。
  3. frame包括数据frame和控制frame

    • 读取到控制frame时,要更新本地控制数据,比如收到window update frame
    • 读取到控制frame时,要对远端控制指定做出一定的反应,比如收到end frame of stream 或者 rst frame,即需要关闭stream,清除本地的stream数据(这里工作由Http2LifecycleManager负责)
    • write数据时,要考虑本地控制model的实际情况,比如流控。
    • write数据时,要对调用方控制指令做出一定的反应,比如调用方发送了end frame of stream

    这也是Http2ConnectionHandler没有直接聚合Http2FrameReader和Http2FrameWriter,而是另提一个Http2ConnectionDecoder、Http2ConnectionEncoder的原因。同时,因为读写逻辑的任务不同,其代码组织也就稍有不同。

从中学到的:

  1. 接口只能指定方法,这个方法可以描述一个功能,也可以描述一个实现类应该具备哪些成员。
  2. 接口只是描述一个角色,而类可以根据自己实现这个角色的便捷性(比如具备所有相关的能力对象),实现多个角色。

对外的使用接口

  1. 配置Http2ConnectionHandler 负责decode数据,decode时会触发Http2FrameListener的执行
  2. 自定义Http2FrameListener,并与Http2ConnectionHandler关联
  3. 使用Http2ConnectionEncoder.writeXX发送frame,write方法的执行要传入ctx对象。

数据接收

Http2ConnectionHandler extends ByteToMessageDecoder.onDecode ==> Http2ConnectionDecoder.decodeFrame ==> Http2FrameReader. readFrame(ChannelHandlerContext ctx, ByteBuf input, Http2FrameListener listener) ==> Http2FrameListener 回调函数

netty对接收数据的抽象,基本上就是各种帧的监听事件。

interface Http2FrameListener{
	int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
               boolean endOfStream) throws Http2Exception;
   void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
        boolean endOfStream) throws Http2Exception;
   void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency,
        short weight, boolean exclusive) throws Http2Exception;
   void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception;
   void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception;
   void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception;
   void onPingRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception;
   void onPingAckRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception;
   void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
        Http2Headers headers, int padding) throws Http2Exception;
   void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData)
        throws Http2Exception;
   void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)
        throws Http2Exception;
   void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload)
        throws Http2Exception;
}

一般情况下

  1. 用户处理onHeadersRead和onDataRead等业务数据
  2. netty http2处理onWindowUpdateRead和onGoAwayRead等控制数据

同时,提供Http2Connection、Http2Stream来存储对应帧、Stream的上下文数据。

DefaultHttp2Connection Http2Connection{
	 IntObjectMap<Http2Stream> streamMap = new IntObjectHashMap<Http2Stream>();
	 DefaultEndpoint<Http2LocalFlowController> localEndpoint;
	 DefaultEndpoint<Http2RemoteFlowController> remoteEndpoint;
	 List<Listener> listeners = new ArrayList<Listener>(4);
	 Promise<Void> closePromise;
}
  1. 存储连接上的Http2Stream
  2. 流控
  3. 监听者
  4. 关闭状态

数据发送

Http2FrameWriter{
	 ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
                           int padding, boolean endStream, ChannelPromise promise);
    ChannelFuture writePriority(ChannelHandlerContext ctx, int streamId, int streamDependency,
        short weight, boolean exclusive, ChannelPromise promise);
   ...
}

写有两种

  1. 非data数据直接发送,this.encoder().writeHeaders ==> Http2FrameWriter.writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endStream, ChannelPromise promise)
  2. data数据由流控组件负责发送,this.encoder().writeData ==> encoder.flowController().addFlowControlled

所以流控是一个大头

流控

netty对http2协议的解析

流控是双向的,根据远程的window update更新流控数据,同时,根据消费数据以及本地空间发送window update。本文主要侧重于前者。

流控组件的接口

流控组件Http2FlowController与外部的接口

  1. 提交数据

    encoder.writeData() ==> flowController().addFlowControlled 将数据加入内部队列。可以看到,这里并没有真正写数据。

  2. 何时真正写数据,写操作时,会根据window size是否富余,来判断是否实际进行读写。:

    • Http2ConnectionHandler extends ByteToMessageDecoder implements ChannelOutBoundHandler
    • ByteToMessageDecoder extends ChannelInboundHandlerAdapter
    • Http2ConnectionHandler 覆盖了ChannelOutBoundHandler的flush方法,执行 encoder.flowController().writePendingBytes();
    • Http2ConnectionHandler 覆盖了ChannelInboundHandlerAdapter 的channelWritabilityChanged、channelReadComplete方法,触发flush方法的执行。基本上,就是可能的window size富余的时候,都试一试。
  3. 更新window size

    DefaultHttp2ConnectionDecoder 本身内置一个 Http2FrameListener,decoder回调onWindowUpdateRead,执行 encoder.flowController().incrementWindowSize(stream, windowSizeIncrement);

流控组件的发送逻辑

首先,连接有一个整体的window size,每个stream也有自己的window size。

encoder.flowController().writePendingBytes(); 触发的是整个连接的发送,在整个连接window size 富余的前提下,从连接里拿出还有富余window size的stream,发送该stream的数据。这就意味这有一个容器维护连接下的stream,这个容器的结构由stream是否具备优先级而定。

对于每一个stream,有两个state

  1. 一个StremByteDistributor.State负责记录它的window size,pending bytes。
  2. 一个UniformStreamByteDistributor.State/WeightedFairQueueByteDistributor.State,负责记录stream的发送状态isWriting,该state主要与Stream的优先级策略有关系。作为载体,封装stream,存储在上述的stream容器中。

两个队列,确切的说是Deque

  1. stream level的,FlowState.pendingWriteQueue
  2. connection level的UniformStreamByteDistributor.queue

以上所述就是小编给大家介绍的《netty对http2协议的解析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

增长黑客

增长黑客

范冰 / 电子工业出版社 / 2015-7-1 / CNY 59.00

“增长黑客”这一概念近年来兴起于美国互联网创业圈,最早是由互联网创业者Sean Ellis提出。增长黑客是介于技术和市场之间的新型团队角色,主要依靠技术和数据的力量来达成各种营销目标,而非传统意义上靠砸钱来获取用户的市场推广角色。他们能从单线思维者时常忽略的角度和难以企及的高度通盘考虑影响产品发展的因素,提出基于产品本身的改造和开发策略,以切实的依据、低廉的成本、可控的风险来达成用户增长、活跃度上......一起来看看 《增长黑客》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换