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