Netty http2 多路复用

Stream

http1是一个请求独占一个链接,这也是被人吐槽的原因,也是http2 要解决的一个痛点,解决方法是在链接的基础上提出了stream的概念,通过stream 来区别不同的请求,在我的另偏博客里写到了,stream是在发送header frame的时候创建的,服务端是在收到header frame时创建的,每个stream都有一个唯一的id标示。

一般rpc 框架要实现多路复用,都在发送的报文里携带请求唯一标示ID,netty http2在handler 层就已经隔离了不同stream之间的frame。

netty 对header frame 解析完后,就开始创建stream,

    public void processFragment(boolean endOfHeaders, ByteBuf fragment,
                Http2FrameListener listener) throws Http2Exception {
            final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
            hdrBlockBuilder.addFragment(fragment, ctx.alloc(), endOfHeaders);
             //endOfHeaders 为true代表header frame全部收到
            if (endOfHeaders) {
                listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), padding,
                                headersFlags.endOfStream());
         }
   }

header frame 接收完成后,开始创建stream,代码如下:

      Http2Stream stream = connection.stream(streamId);
      boolean allowHalfClosedRemote = false;
      if (stream == null && !connection.streamMayHaveExisted(streamId)) {
           //这里创建,虽然只有一行代码,单这里面基本是复用的核心。
           stream = connection.remote().createStream(streamId, endOfStream);
           // Allow the state to be HALF_CLOSE_REMOTE if we're creating it in that state.
          allowHalfClosedRemote = stream.state() == HALF_CLOSED_REMOTE;
      }

createStream 的代码如下,由DefaultHttp2Connection 实现,DefaultHttp2Connection对应一个物理链接。

   public DefaultStream createStream(int streamId, boolean halfClosed) throws Http2Exception {
        State state = activeState(streamId, IDLE, isLocal(), halfClosed);

        checkNewStreamAllowed(streamId, state);

        // Create and initialize the stream.
        DefaultStream stream = new DefaultStream(streamId, state);

        incrementExpectedStreamId(streamId);

        addStream(stream);
        //关键是这里,创建完stream后,激活该stream
        stream.activate();
        return stream;
    }

stream.activate() 会调用到ConnectionListener的onStreamActive0方法,ConnectionListener 是connection 上创建stream的监听器。

private void onStreamActive0(Http2Stream stream) {
    if (connection().local().isValidStreamId(stream.id())) {
        return;
    }

   //这里创建了一个DefaultHttp2FrameStream,newStream()方法
    DefaultHttp2FrameStream stream2 = newStream().setStreamAndProperty(streamKey, stream);
    onHttp2StreamStateChanged(ctx, stream2);
}

onHttp2StreamStateChanged方法被Http2MultiplexCodec 重写了,正是Http2MultiplexCodec 通过stream的active事件,是实现多路复用的。

final void onHttp2StreamStateChanged(ChannelHandlerContext ctx, Http2FrameStream stream) {
    //上面的newStream()也是重写的,创建了一个Http2MultiplexCodecStream,
    Http2MultiplexCodecStream s = (Http2MultiplexCodecStream) stream;
    //创建的stream是open状态。
    switch (stream.state()) {
        case HALF_CLOSED_REMOTE:
        case OPEN:
            if (s.channel != null) {
                // ignore if child channel was already created.
                break;
            }
            // fall-trough
            //这里是关键,新的stream时,会新创建一个DefaultHttp2StreamChannel,该DefaultHttp2StreamChannel实现了netty的channel,也是我们自己定义的handler的上下文里传递的channel,这正是和http1不同的地方。
            ChannelFuture future = ctx.channel().eventLoop().register(new DefaultHttp2StreamChannel(s, false));
            if (future.isDone()) {
                registerDone(future);
            } else {
                future.addListener(CHILD_CHANNEL_REGISTRATION_LISTENER);
            }
            break;
        case CLOSED:
            DefaultHttp2StreamChannel channel = s.channel;
            if (channel != null) {
                channel.streamClosed();
            }
            break;
        default:
            // ignore for now
            break;
    }
}

通过上面的代码我们可以看出,每次创建一个stream时,netty的Http2MultiplexCodec 解码器会创建一个DefaultHttp2StreamChannel,并register到event loop上,关键是该register方法会调用该channel的unsafe的register方法,DefaultHttp2StreamChannel由自己的unsafe,叫Http2ChannelUnsafe,下面我们看下该Http2ChannelUnsafe的register方法。

  public void register(EventLoop eventLoop, ChannelPromise promise) {
            if (!promise.setUncancellable()) {
                return;
            }
            if (registered) {
                throw new UnsupportedOperationException("Re-register is not supported");
            }

            registered = true;

            //每次新建的DefaultHttp2StreamChannel的outbound为false,
            if (!outbound) {
                // Add the handler to the pipeline now that we are registered.
                pipeline().addLast(inboundStreamHandler);
            }

            promise.setSuccess();
            //下发register事件,即每一个http2请求你自己定义的handlser都会收到一个register事件。
            pipeline().fireChannelRegistered();
            if (isActive()) {
                pipeline().fireChannelActive();
            }
   }

通过上面的分析,总结下,在http2的协议下,netty实现多路复用,是通过为每个http2请求创建一个channel即DefaultHttp2StreamChannel,并创建对应的pipeline,该pipeline上的所有handler都会new一个,这样就保证了不同stream之间的数据读是独立的,不会产生错乱。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容