导读
原创文章,转载请注明出处。
本文源码地址: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<NioSocketChannel>() {
@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<NioSocketChannel>() {
@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、C3个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框架、分布式调用跟踪、监控系统等。爱技术、爱学习,欢迎联系交流。