导读
原创文章,转载请注明出处。
本文源码地址:netty-source-code-analysis
本文所使用的netty版本4.1.6.Final:带注释的netty源码
在"Pipeline的构造"这一节中我们已经讲过了,Pipeline
中利用了责任链模式,而发挥责任链功能的数据结构就是由多个HandlerContext
构成的的双向链表,而每一个HandlerContext
又对应一个ChannelHandler
(组合模式或者继承)。由于每一个HandlerContext
对应一个ChannelHandler
,所以本文行文中有时候会把HandlerContext
和ChannelHandler
等同对待,例如文章中
从当前
HandlerContext
(tail
)开始向head
方向遍历寻找下一个ChannelOutboundHandler
实际上准确来说应该是
从当前
HandlerContext
(tail
)开始向head
方向遍历寻找下一个HandlerContext
,并且该HandlerContext
包含或者实现了ChannelOutboundHandler
。
为了行文不那么拗口,我们就把HandlerContext
和ChannelHandler
等同对待了。
本文我们以客户端建立连接、收发数据为例来学习一下Pipeline
的工作原理。
1 ChannelHandler方法概览
在ChannelPipeline
的构造这篇文章中我们提到过ChannelHandler
,Pipeline
中双向链表由HandlerContext
组成,而每一个HandlerContext
又包含一个ChannleHandler
。我们看一下其中的主要方法,这是所有ChannelHandler
的公共接口,公共接口中主要有两个方法handlerAdded
和handlerRemoved
,这是在ChannelHandler
添加和删除完成之后的回调方法。上一篇文章中我们已经分析过Pipeline
中ChannelHandler
的添加和删除了,这里不再赘述。
public interface ChannelHandler {
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
@Deprecated
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
}
我们重点来看ChannelHandler
的两个子接口,ChannelInboundHandler
和ChannelOutboundHandler
。
首先来看ChannelInboundHandler
,ChannelInboundHandler
类上的注释如下
which adds callbacks for state changes. This allows the user
to hook in to state changes easily.
翻译过来就是“Channel状态改变时的回调方法”。其中的方法名多为Channel
+ 动词过去分词的形式。
public interface ChannelInboundHandler extends ChannelHandler {
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
void channelActive(ChannelHandlerContext ctx) throws Exception;
void channelInactive(ChannelHandlerContext ctx) throws Exception;
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
再来看ChannelOutboundHandler
,ChannelOutboundHandler
上的注释如下
which will get notified for IO-outbound-operations.
翻译过来就是“当发生IO出站操作时的回调方法”,其中的方法名多为动词原型的形式。
public interface ChannelOutboundHandler extends ChannelHandler {
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
void connect(
ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception;
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void read(ChannelHandlerContext ctx) throws Exception;
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
void flush(ChannelHandlerContext ctx) throws Exception;
}
2 ChannelInboundInvoker和ChannelOutboundInvoker
和本文相关的组件中,ChannelPipeline
和ChannelHandlerContext
都实现了ChannelInboundInvoker
和ChannelOutboundInvoker
接口。
ChannelInboundInvoker
内的方法的特点是大部分都是以fire
开头,并且是fire
+ 动词的过去分词的形式。我们知道过去分词是表被动的意思,所以这里呢,一般是Channel
里发生了某些“被动事件”之后调用的。
public interface ChannelInboundInvoker {
ChannelInboundInvoker fireChannelRegistered();
ChannelInboundInvoker fireChannelUnregistered();
ChannelInboundInvoker fireChannelActive();
ChannelInboundInvoker fireChannelInactive();
ChannelInboundInvoker fireExceptionCaught(Throwable cause);
ChannelInboundInvoker fireUserEventTriggered(Object event);
ChannelInboundInvoker fireChannelRead(Object msg);
ChannelInboundInvoker fireChannelReadComplete();
ChannelInboundInvoker fireChannelWritabilityChanged();
}
ChannelOutboundInvoker
内的方法的特点是除了后边几个以new
开头的方法之外,都是以单个或者两个动词原型的形式作为方法名称,比如read
、write
和writeAndFlush
。这里直接用动词原型就是表示主动的意思,所以这里一般是向Channel
发送主动命令
使用的。
public interface ChannelOutboundInvoker {
ChannelFuture bind(SocketAddress localAddress);
ChannelFuture connect(SocketAddress remoteAddress);
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress);
ChannelFuture disconnect();
ChannelFuture close();
ChannelFuture deregister();
ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise);
ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise);
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
ChannelFuture disconnect(ChannelPromise promise);
ChannelFuture close(ChannelPromise promise);
ChannelFuture deregister(ChannelPromise promise);
ChannelOutboundInvoker read();
ChannelFuture write(Object msg);
ChannelFuture write(Object msg, ChannelPromise promise);
ChannelOutboundInvoker flush();
ChannelFuture writeAndFlush(Object msg, ChannelPromise promise);
ChannelFuture writeAndFlush(Object msg);
ChannelPromise newPromise();
ChannelProgressivePromise newProgressivePromise();
ChannelFuture newSucceededFuture();
ChannelFuture newFailedFuture(Throwable cause);
ChannelPromise voidPromise();
我们来仔细分析一下上文中提到的ChannelInboundHandler
、ChannelInboundInvoker
、ChannelOutboundHandler
和ChannelOutboundInvoker
。
我们看到ChannelInboundHandler
和ChannelInboundInvoker
中的方法非常相似,ChannelInboundHandler
中的方法名多以Channel
加动词过去分词的形式组成,而ChannelInboundInvoker
中的方法就是ChannelInboundHandler
中的方法加上fire
构成。
ChannelOutboundHandler
和ChannelOutboundInvoker
中的方法名几乎一模一样,都是以动词原形作为方法名。
我们来仔细琢磨一下这些方法的命名,Channel
加动词过去分词表示该Channel
发生了某种事件,而fireChannel
加动词过去分词表示在该Channel
上触发这种事件。例如ChannelRead
表示该Channel
上读到了数据事件,fireChannelRead
表示在该Channel
上触发ChannelRead
事件。
直接用动词原形作为方法名,表示命令该Channel
进行某种操作,例如read
表示命令该Channel
进行数据的读取。为什么ChannelOutboundInvoker
中的方法名没有以fire
开头呢,因为ChannelOutbound
传播的不是事件,而是一种主动的操作。
还有同学记得我在“Netty整体架构”这篇文章中提到的“事件”和“命令”吗,在某些地方翻译成“入站事件”和“出站事件”,我个人认为翻译成“事件”和“命令”更容易理解,因为我们感知到“事件”的发生是被动的,而发出“命令”是主动的,都翻译成“事件”不容易理解。
3 以客户端为例分析Pipeline工作原理
本文的示例代码在package com.zhongdaima.netty.analysis.pipeline
中。
服务端示例代码如下,这个服务端的功能就是将所有客户端发过来的数据原封不动地返回。
/**
* 欢迎关注公众号“种代码“,获取博主微信深入交流
*
* @author wangjianxin
*/
public class ServerBoot {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(1);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//连接发过来的数据原样返回
if (msg instanceof ByteBuf) {
ctx.write(msg);
ctx.flush();
}
}
});
}
});
ChannelFuture f = b.bind(8000).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
客户端的示例代码如下,在客户端中我们添加了3个ChannelHandler
,分别是A
,B
,C
。
/**
* 欢迎关注公众号“种代码“,获取博主微信深入交流
*
* @author wangjianxin
*/
public class ClientBoot {
public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast("A", new AInBoundHandler());
ch.pipeline().addLast("B", new BOutBoundHandler());
ch.pipeline().addLast("C", new CDuplexHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8000);
Channel channel = channelFuture.syncUninterruptibly().channel();
channel.write(Unpooled.wrappedBuffer("Hello, 种代码".getBytes()));
channel.flush();
channel.closeFuture().awaitUninterruptibly();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
AInboundHandler
继承自ChannelInboundHandlerAdapter
,ChannelInboundHandlerAdapter
是Netty
中ChannelInboundHandler
的默认实现,AInboundHandler
是一个ChannelInboundHandler
。
public class AInboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("ChannelActive in A");
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
byte[] bytes = new byte[((ByteBuf) msg).readableBytes()];
//这里调用getBytes不会导致readerIndex的移动
((ByteBuf) msg).getBytes(0, bytes);
System.out.println("ChannelRead in A, msg=" + new String(bytes));
}
super.channelRead(ctx, msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("ExceptionCaught in A, cause=" + cause);
super.exceptionCaught(ctx, cause);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("HandlerAdded in A");
super.handlerAdded(ctx);
}
}
BOutboundHandler
继承自ChannelOutboundHandlerAdapter
,ChannelOutboundHandlerAdapter
是netty中``ChannelOutboundHandler的默认实现,
BOutboundHandler是一个
ChannelOutboundHandler`。
public class BOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
System.out.println("Connect in B");
super.connect(ctx, remoteAddress, localAddress, promise);
}
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
System.out.println("Read in B");
super.read(ctx);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof ByteBuf){
byte[] bytes = new byte[((ByteBuf) msg).readableBytes()];
//这里调用getBytes不会导致readerIndex的移动
((ByteBuf) msg).getBytes(0, bytes);
System.out.println("Write in B, msg=" + new String(bytes));
}
super.write(ctx, msg, promise);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("HandlerAdded in B");
super.handlerAdded(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("ExceptionCaught in B, cause=" + cause);
super.exceptionCaught(ctx, cause);
}
}
CDuplexHandler
继承自ChannelDuplexHandler
,ChannelDuplexHandler
同时实现了ChannelInboundHandler
和ChannelOutboundHandler
,所以CDuplexHandler
既是一个ChannelInboundHandler
又是一个ChannelOutboundHandler
。
public class CDuplexHandler extends ChannelDuplexHandler {
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
System.out.println("Connect in C");
super.connect(ctx, remoteAddress, localAddress, promise);
}
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
System.out.println("Read in C");
super.read(ctx);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof ByteBuf) {
byte[] bytes = new byte[((ByteBuf) msg).readableBytes()];
//这里调用getBytes不会导致readerIndex的移动
((ByteBuf) msg).getBytes(0, bytes);
System.out.println("Write in C, msg=" + new String(bytes));
}
super.write(ctx, msg, promise);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("ChannelActive in C");
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
byte[] bytes = new byte[((ByteBuf) msg).readableBytes()];
//这里调用getBytes不会导致readerIndex的移动
((ByteBuf) msg).getBytes(0, bytes);
System.out.println("ChannelRead in C, msg=" + new String(bytes));
}
super.channelRead(ctx, msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("ExceptionCaught in C, cause=" + cause);
super.exceptionCaught(ctx, cause);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("HandlerAdded in C");
super.handlerAdded(ctx);
}
}
在添加完这3个Handler
之后,我们来看一下客户端的Pipeline
状态,当前Pipeline
的状态如下图所示。除了内置的HeadContext
和TailContext
之外,还有我们手动添加的A
、B
、C
3个Handler
。
先启动服务端,再启动客户端,我们可以在客户端的控制台中看到如下输出。
Connect in C
Connect in B
ChannelActive in A
ChannelActive in C
Read in C
Read in B
Write in C, msg=Hello, 种代码
Write in B, msg=Hello, 种代码
ChannelRead in A, msg=Hello, 种代码
ChannelRead in C, msg=Hello, 种代码
Read in C
Read in B
4 ChannelPipeline的方向
我们在Pipeline
的构造中提到过Pipeline
中有两个特殊的ChannelHandlerContext
,分别是HeadContext
和TailContext
。既然有了Head
和Tail
那必然是有方向了,那Pipeline
的方向是什么样的呢,我们看下面这张图。
和Channel
直接接触的方向是Head
,另一端是Tail
。
在“Netty整体架构”这篇文章中也提到过ChannelInboundHandler
和ChannelOutboundHandler
,那Inbound
和Outbound
又是哪个方向呢。我们站在应用的位置来看,即站在BizHandler
的位置来看,从Channel
读取数据进入到我们的应用即是Inbound
,而从我们的应用向Channel
写数据即是Outbound
。
5 Pipeline的工作原理
我们在前面的文章中在牵涉到Pipeline
的地方都一笔带过了,今天咱们来详细分析一下曾经略过的内容。
5.1 connect的传播
在我们调用bootstrap.connect("127.0.0.1", 8000)
这行代码进行连接时最终会调用到AbstractChannel#connect
方法,该方法逻辑很简单,直接调用了pipeline
的connect
方法,咱们跟进去看。
@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
return pipeline.connect(remoteAddress, promise);
}
Pipeline
的connect
方法同样很简单,调用了tail
的connect
方法,TailContext
继承自AbstractChannelHandlerContext
,并没有覆盖connect
方法,所以tail.connect
调用到了AbstractChannelHandlerContext
中的connect
方法。
public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
return tail.connect(remoteAddress, promise);
}
AbstractChannelHandlerContext
中的connect
方法,先是调用findContextOutbound
,找到下一个ChannelOutboundHandler
,这个方法很简单,就是从当前HandlerContext
(tail
)开始向head
方向遍历寻找下一个ChannelOutboundHandler
,在我们的示例中,tail
的下一个ChannelOutboundHandler
就是我们所添加的CDuplexHandler
,找到之后调用了next.invokeContext
方法。
@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
return connect(remoteAddress, null, promise);
}
@Override
public ChannelFuture connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
//从当前HandlerContext向`head`方向查找下一个包含`ChannelOutboundHandler`的`HandlerContext`
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeConnect(remoteAddress, localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeConnect(remoteAddress, localAddress, promise);
}
}, promise, null);
}
return promise;
}
invokeConnect
方法首先调用invokeHandler()
方法判断当前Handler
是否已经添加完成(当前Handler
的handlerAdded
方法已经被调用过)。如果返回true
就调用当前Handler
的connect
方法,这就是我们的CDuplexHandler
的connect
方法被调用到的地方,也就是我们在控制台看到的第一个输出Connect in C
。如果返回false
就继续调用HandlerContext
的connect
方法继续查找下一个ChannelOutboundHanlder
。
private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
//判断当前Handler是否已经完成添加(即handlerAdded方法被调用过了)
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
connect(remoteAddress, localAddress, promise);
}
}
回到咱们的CDuplexHandler
的connect
方法,打印完Connect in C
之后调用了super.connect
方法,即ChannelDuplexHandler#connect
方法。
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
System.out.println("Connect in C");
super.connect(ctx, remoteAddress, localAddress, promise);
}
ChannelDuplexHandler#connect
方法调用了它所在的HandlerContext
的connect
方法,即AbstractChannelHandlerContext#connect
方法,熟悉的身影又回来了,咱们刚刚分析过这个方法,即tail.connect
,而TailContext
未覆盖这个方法,tail.connect
就是ChannelDuplexHandler#connect
。
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception {
ctx.connect(remoteAddress, localAddress, promise);
}
显而易见下一个ChannelOutboundHander
就是BOutboundHandler
,所以我们看到控制台的第二行输出Connect in B
。
最后一个ChannelOutboundHandler
是HeadContext
,我们到HeadContext
来看一下它的connnect
方法。HeadContext
的connect
方法很简单,直接调用了unsafe
的connect
方法,而unsafe.connect
方法我们在“客户端的启动过程”这篇文章中已经分析过了,不再赘述。
@Override
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
unsafe.connect(remoteAddress, localAddress, promise);
}
5.2 ChannelActive的传播
我们在“客户端的启动过程”这篇文章中提到过AbstractNioUnsafe#finishConnect
方法,当NioEventLoop
检测到Channel
上有OP_CONNECT
事件发生时,会调用unsafe.finishConnect
方法。
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
finishConnect
方法在AbstratNioUnsafe
中。在调用完doFinishConnect
方法之后会调用fulfillConnectPromise
方法,我们一起来看一下。
@Override
public final void finishConnect() {
try {
boolean wasActive = isActive();
doFinishConnect();
fulfillConnectPromise(connectPromise, wasActive);
} catch (Throwable t) {
} finally {
}
}
fulfillConnectPromise
方法里有很多内容,这里咱们只关注本次要讲的重点,pipeline().fireChannelActive()
。
private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
if (!wasActive && active) {
pipeline().fireChannelActive();
}
}
我们看一下pipeline
的fireChannelActive
方法,这里调用了invokeChannelActive
方法,传递的参数是HeadContext
。
@Override
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
invokeChannelActive
方法调用了next.invokeChannelActive
,这里的next
就是HeadContext
。
static void invokeChannelActive(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelActive();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelActive();
}
});
}
}
invokeChannelActive
方法调用了Handler
的channelActive
方法,这里的Handler
自然也是HeadContext
。
private void invokeChannelActive() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelActive(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelActive();
}
}
HeadContext
里的channelActive
方法调用了ct.fireChannelActive()
,显然这里的ctx
就是HeadContext
本身。
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
readIfIsAutoRead();
}
fireChannelActive
方法在AbstractChannelHandlerContext
中,它首先从当前HandlerContext
查找下一个ChannelInboundHandler
,然后调用invokeChannelActive
方法,熟悉的身影又回来了,invokeChannelActive
方法咱们刚刚也见过。刚才调用invokeChannelActive
方法是由Pipeline
发起的,直接传递了head
作为参数,而这里是由head
发起的,参数是从head
开始的下一个包含ChannelInboundHandler
,显然这个参数是我们所添加的AInboundHandler
,所以我们在控制台看到ChannelActive in A
。
@Override
public ChannelHandlerContext fireChannelActive() {
invokeChannelActive(findContextInbound());
return this;
}
继续下去可以想见AInboundHandler
被调用后也会查找下一个ChannelInboundHandler
就是CDuplexHandler
,所以我们在控制台看到的下一条输出就是ChannelActive in C
。
5.3 read的传播
刚才咱们在看HeadContext
里的channelActive
方法时,其中并不仅仅只有ctx.fireChannelActive()
调用,紧跟着的就是readIfIsAutoRead()
的调用。咱们一起来看一下。这里判断channel
是否配置了自动读取,默认情况下是true
,接着调用了channel.read()
。
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
channel.read
方法在AbstractChannel
中,调用了pipeline.read
方法。
@Override
public Channel read() {
pipeline.read();
return this;
}
pipeline
的read
方法调用了tail.read
,这里的tail
就是指TailContext
了。
@Override
public final ChannelPipeline read() {
tail.read();
return this;
}
tail
没有覆盖read
方法,这个方法的实现在AbstractChannelHandlerContext
中,首先调用findContextOutbound
方法查找下一个ChannelOutboundHandler
。第一个查找到的就是CDuplexHandler
,调用了它的invokeRead
方法。
@Override
public ChannelHandlerContext read() {
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeRead();
} else {
Runnable task = next.invokeReadTask;
if (task == null) {
next.invokeReadTask = task = new Runnable() {
@Override
public void run() {
next.invokeRead();
}
};
}
executor.execute(task);
}
return this;
}
invokeRead
方法在AbstractChannelHandlerContext
中,invokeRead
调用了它所包含的ChannelInboundHandler
的channelRead
方法,即CDuplexHandler
的channelRead
方法,在控制台打印出Read in C
。
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
CDuplexHandler
的channelRead
方法在打印完成后调用了super.read(ctx)
,我们跟下去看一下。
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
System.out.println("Read in C");
super.read(ctx);
}
接着调用了ctx.read()
,即AbstractChannelHandlerContext#read
方法,熟悉的方法又出现了,接着调用下去就到了下一个ChannelOutboundHandler
即BOutboundHandler
,在控制台打印出Read in B
。
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
ctx.read();
}
接着往下调用就到了HeadContext
,HeadContext
的read
方法调用了unsafe.beginRead
@Override
public void read(ChannelHandlerContext ctx) {
unsafe.beginRead();
}
unsafe
的beginRead
方法调用了doBeginRead
。
@Override
public final void beginRead() {
assertEventLoop();
if (!isActive()) {
return;
}
try {
doBeginRead();
} catch (final Exception e) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireExceptionCaught(e);
}
});
close(voidPromise());
}
}
我们以AbstractNioChannel
为例看一下doBeginRead
方法。这里的doBeginRead
方法把readInterestOp
加入到兴趣事件中,readInterestOp
在构造方法中被赋值,即是OP_READ
兴趣事件。
@Override
protected void doBeginRead() throws Exception {
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
调用read
方法,并不能真正从Channel
中读取数据,只是把OP_READ
兴趣事件加入到selectionKey
中。
5.4 write的传播
在完成连接后,我们在客户端代码中调用了channel.write(Unpooled.wrappedBuffer("Hello, 种代码".getBytes()));
向服务端发送数据。咱们跟进去看看,这里的channel.write
方法在AbstractChannel
中,直接调用了pipeline.write
方法。
@Override
public ChannelFuture write(Object msg) {
return pipeline.write(msg);
}
pipeline
的write
方法调用了tail.write
方法,tail
即是TailContext
。
@Override
public final ChannelFuture write(Object msg) {
return tail.write(msg);
}
咱们就不再一步步跟下去了,从控制台打印出的Write in C, msg=Hello, 种代码
和Write in B, msg=Hello, 种代码
来推测,write
命令从tail
开始经过了CDuplexHandler
和BOutboundHandler
最后到达HeadContext
。
5.5 ChannelRead的传播
在服务端接收到数据之后,直接将数据原样返回,当Eventloop
发现Channel
中发生OP_READ
事件时,调用unsafe.read()
进行数据的读取。这部分代码咱们在讲EventLoop
的时候已经提到过,在NioEventLoop
的processSelectedKey
方法中。
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
咱们跟进去看看unsafe.read
方法,方法实现在NioByteUnsafe
中,其中分配缓冲区读取数据的过程咱们先略过,这篇文章咱们着重介绍Pipeline
,直接看读取数据之后调用了什么方法。在读取数据之后(所分配的缓冲区填满)调用了pipeline.fireChannelRead(byteBuf)
,而在读完数据之后(tcp缓冲区数据被读完)调用了pipeline.fireChannelReadComplete()
。
@Override
public final void read() {
try {
do {
//分配缓冲区
byteBuf = allocHandle.allocate(allocator);
//读取数据
allocHandle.lastBytesRead(doReadBytes(byteBuf));
//触发ChannelRead事件
pipeline.fireChannelRead(byteBuf);
} while (allocHandle.continueReading());
//触发ChannelReadComplete事件
pipeline.fireChannelReadComplete();
} catch (Throwable t) {
} finally {
}
}
}
先看看``pipeline.fireChannelRead(byteBuf)方法,很简单,这里调用了
AbstractChannelHandlerContext.invokeChannelRead方法,并传入参数
head,即是
HeadContext`。
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
invokeChannelRead
方法又调用了next.invokeChannelRead
,这里的next
即HeadContext
。
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}
到这儿里咱们也不再一步步跟下去了,根据控制台打印出的ChannelRead in A, msg=Hello, 种代码
和ChannelRead in C, msg=Hello, 种代码
来推测,ChannelRead
事件从HeadContext
开始经过AInboundHandler
,和CDuplexHandler
到达TailContext
。
最后在控制台又打印出“Read in C”和“Read in B”是怎么回事呢,那是因为在pipeline.fireChannelReadComplete()
又发出了read
命令,咱们不再赘述。
6 总结
-
ChannelOutboundInvoker
:用来向Channel
发送“命令”的接口。 -
ChannelInboundInvoker
:用来触发Channel
上发生“事件”的接口。 -
ChannelOutboundHandler
:“命令”在Pipeline
上传播时的回调方法。 -
ChannelInboundHandler
: “事件”在Pipeline
上传播时的回调方法。 - 在
Pipeline
中主动发出的命令,例如connect
、read
、write
等由TailContext
开始依次经过所有的ChannelOutboundHandler
,最后到达HeadContext
。 - 在
Pipeline
中被动发生的事件,例如ChannelActive
、ChannelRead
等由HeadContext
开始依次经过所有的ChannelInboundHandler
,最后到达TailContext
。
关于作者
王建新,转转架构部资深Java工程师,主要负责服务治理、RPC框架、分布式调用跟踪、监控系统等。爱技术、爱学习,欢迎联系交流。