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之间的数据读是独立的,不会产生错乱。

你可能感兴趣的:(Netty http2 多路复用)