前言
在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的多路复用做支持。
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,默认只有HeadContext和TailContext,可以在实例化Http2MultiplexHandler时传入针对子Channel的ChannelHandler。
另一个比较重要的方法是Http2MultiplexHandler#channelRead(),它对RESET和GOAWAYFrame进行了处理,WINDOW_UPDATEFrame也不会传播给子Channel,因为在Http2FrameCodec中已经被处理过了。