Netty对HTTP2多路复用的支持

前言

在HTTP/1.1中,连接可以被复用,但是连接上的请求-响应是串行的,如果要同时处理多个请求-响应,不得不开启多个连接,带来的问题就是消耗大量的资源。
HTTP/1.x时代,TCP连接远远没有被有效利用,于是HTTP2带来了多路复用的新特性。

多路复用

多路复用是指在HTTP/2中,可以同时发送多个请求和接收多个响应,而不需要为每个请求/响应建立新的连接。
在HTTP/2的多路复用中,每个请求和响应都会被分配一个唯一的标识符,称为StreamID。通过使用StreamID,客户端和服务器可以区分不同的请求和响应。这样,它们可以并行地发送和接收多个流,提高了网络通信的效率。

多路复用还解决了HTTP/1.1中的"队头阻塞"问题。在HTTP/1.1中,如果某个请求的响应延迟,那么所有后续的请求都必须等待该响应完成后才能发送。这导致了一个请求的延迟会影响到整个页面的加载速度。而在HTTP/2的多路复用中,由于可以并行发送多个请求和接收多个响应,一个请求的延迟不会阻塞其他请求,提高了整体的响应时间和并发性能。

Http2MultiplexHandler

Netty很早就支持HTTP2,同样的,它提供了一个类io.netty.handler.codec.http2.Http2MultiplexHandler来对HTTP2的多路复用做支持。
Netty对HTTP2多路复用的支持_第1张图片
Http2MultiplexHandler的类图还算简单,继承了io.netty.channel.ChannelDuplexHandler,代表它可以同时处理入站和出站事件,这也在意料之中,因为HTTP2的Channel本来就是双向的。

Http2MultiplexHandler必须配合Http2FrameCodec使用,因为Http2MultiplexHandler本身不具备HTTP2 Frame的编解码能力,只是对多路复用提供了支持,它得先依赖Http2FrameCodec将字节序列解码成HTTP2 Frame。
Http2MultiplexHandler#userEventTriggered():是比较核心的方法,它用来处理用户事件。Http2FrameCodec解析到HEADERSFrame时,首先会判断streamId是否存在,不存在就意味着当前Stream是新创建的,此时会传播一个Http2FrameStreamEvent用户事件。Http2MultiplexHandler收到事件后,判断Stream.state,如果是open意味着需要开启一个新的Stream。
对于HTTP2来说,Stream是虚拟的概念,但是对于Netty来说,会为每个Stream创建一个单独的Channel,它的父Channel指向连接的Channel,子Channel会和父Channel共享一些资源,但是数据的读写是独立的。
子Channel和父Channel注册到同一个EventLoop,这意味着由同一个线程来负责数据读写。

@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    if (evt instanceof Http2FrameStreamEvent) {
        Http2FrameStreamEvent event = (Http2FrameStreamEvent) evt;
        DefaultHttp2FrameStream stream = (DefaultHttp2FrameStream) event.stream();
        if (event.type() == Http2FrameStreamEvent.Type.State) {
            switch (stream.state()) {
                case HALF_CLOSED_LOCAL:
                    if (stream.id() != Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) {
                        break;
                    }
                case HALF_CLOSED_REMOTE:
                case OPEN:
                    // 如果状态是OPEN 需要新建Channel
                    if (stream.attachment != null) {
                        break;
                    }
                    final AbstractHttp2StreamChannel ch;
                    // 协议协商的stream
                    if (stream.id() == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID && !isServer(ctx)) {
                        if (upgradeStreamHandler == null) {
                            throw connectionError(INTERNAL_ERROR,
                                    "Client is misconfigured for upgrade requests");
                        }
                        ch = new Http2MultiplexHandlerStreamChannel(stream, upgradeStreamHandler);
                        ch.closeOutbound();
                    } else {
                        // 为Stream创建Channel
                        ch = new Http2MultiplexHandlerStreamChannel(stream, inboundStreamHandler);
                    }
                    // 注册到父Channel所属的EventLoop,由同一个线程来负责数据读写
                    ChannelFuture future = ctx.channel().eventLoop().register(ch);
                    if (future.isDone()) {
                        registerDone(future);
                    } else {
                        future.addListener(CHILD_CHANNEL_REGISTRATION_LISTENER);
                    }
                    break;
                case CLOSED:
                    AbstractHttp2StreamChannel channel = (AbstractHttp2StreamChannel) stream.attachment;
                    if (channel != null) {
                        channel.streamClosed();
                    }
                    break;
                default:
                    // ignore for now
                    break;
            }
        }
        return;
    }
    ctx.fireUserEventTriggered(evt);
}

子Channel有自己单独的Pipeline,默认只有HeadContextTailContext,可以在实例化Http2MultiplexHandler时传入针对子Channel的ChannelHandler。

另一个比较重要的方法是Http2MultiplexHandler#channelRead(),它对RESETGOAWAYFrame进行了处理,WINDOW_UPDATEFrame也不会传播给子Channel,因为在Http2FrameCodec中已经被处理过了。

你可能感兴趣的:(HTTP2,Netty,http,netty)