(*文章基于Netty4.1.22版本)
介绍
Netty中随着一个Channel的创建,会连带创建一个ChannelPipeline,这个ChannelPipeline就像一个处理各种事件的管道,负责去处理Channel上发生的事件,例如连接事件,读事件,写事件等。
更深入的说,处理的并不是ChannelPipeline,而是ChannelPipeline中一个个的ChannelHandler,其结构如下
ChannelPipeline中有很多Handler(其实是Context类型,Context封装了Handler),组成了一个双向的链表,同时初始化的时候就会带有一个头结点和尾结点,自定义的ChannelHandler都会添加到Head和Tail之间。
同时Netty定义了两种事件:
- inbound:事件从Head往Tail方向传递,实现ChannelInboundHandler的ChannelHandler为处理inbound事件的ChannelHandler
- outbound:事件从Tail往Head方向传递,实现ChannelOutboundHandler的ChannelHandler为处理inbound事件的ChannelHandler
ChannelPipeline如何与ChannelHandler关联
先看下ChannelPipeline接口的部分定义
public interface ChannelPipeline
extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable<Entry<String, ChannelHandler>> {
ChannelPipeline addFirst(String name, ChannelHandler handler);
ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler);
ChannelPipeline addLast(String name, ChannelHandler handler);
ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);
ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler);
ChannelPipeline addBefore(EventExecutorGroup group, String baseName, String name, ChannelHandler handler);
ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler);
ChannelPipeline addAfter(EventExecutorGroup group, String baseName, String name, ChannelHandler handler);
ChannelPipeline addFirst(ChannelHandler... handlers);
ChannelPipeline addFirst(EventExecutorGroup group, ChannelHandler... handlers);
ChannelPipeline addLast(ChannelHandler... handlers);
ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers);
}
通过方法名称,大概可以看出其工作原理以及每个方法是做什么的
再看下Channel启动初始化的时候,默认是DefaultChannelPipeline(从这里可以看出,Channel总是对应一个pipeline)
protected AbstractChannel(Channel parent) {
//....
pipeline = newChannelPipeline();
}
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
那么看下DefaultChannelPipeline的对方法的实现
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
默认初始化了Head和Tail,并且将ChannelPipeline对应的Channel也保存了下来。
接下来,以文章Netty源码分析—-服务启动之Channel初始化中的Netty的demo中的这句代码为例,分析一下其中实现
socketChannel.pipeline().addLast(new NettyServerHandler());
这里将一个自定义的ChannelHandler加入到了ChannelPipeline中
public final ChannelPipeline addLast(ChannelHandler handler) {
return addLast(null, handler);
}
@Override
public final ChannelPipeline addLast(String name, ChannelHandler handler) {
return addLast(null, name, handler);
}
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);//判断是否重复添加并设置add属性为true
// 将Handler封装成AbstractChannelHandlerContext
newCtx = newContext(group, filterName(name, handler), handler);
addLast0(newCtx);
// 这就是服务启动那篇文章讲过的
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
// executor属性如果为空,则返回EventLoop
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {// 异步执行任务
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
newContext主要创建了一个DefaultChannelHandlerContext对象,看下其构造方法
DefaultChannelHandlerContext(
DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
}
//父类的构造方法
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name,
boolean inbound, boolean outbound) {
this.name = ObjectUtil.checkNotNull(name, "name");
this.pipeline = pipeline;
this.executor = executor;
this.inbound = inbound;
this.outbound = outbound;
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
保存了Handler已经所属的Pipeline,还有这个Handler属于inbound还是outbound等属性,以及触发事件在Pipeline中的传播,后续会分析。
主要看下addLast0方法的实现:
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
逻辑很简单,将Context加入到Tail前面,链表的相关知识,不再分析引用的变化过程。
其他方法也是类似的过程,此处省略
InBound事件在Pipeline中传播
以注册时间为例,在之前讲过,注册调用的是AbstractUnsafe的register0方法,其中有句代码如下:
pipeline.fireChannelRegistered();
在这里就触发了事件的传播,看下其实现
public final ChannelPipeline fireChannelRegistered() {
AbstractChannelHandlerContext.invokeChannelRegistered(head);
return this;
}
直接调用了静态方法,传入Head,表示从Head开始传播
“` java
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
}
next是HeadContext,看下他的invokeChannelRegistered方法(实际在父类AbstractChannelHandlerContext中)
``` java
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
<div class="se-preview-section-delimiter"></div>
这里又调回了HeadContext的channelRegistered方法
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
invokeHandlerAddedIfNeeded();
ctx.fireChannelRegistered();
}
<div class="se-preview-section-delimiter"></div>
invokeHandlerAddedIfNeeded方法这个在服务启动的文章中分析过,回调在Channel注册前添加的Handler,该方法只会调用一次。
再看下fireChannelRegistered方法,这个实现在父类AbstractChannelHandlerContext中实现,没有传入参数,这个时候会去寻找下一个节点,进行调用
@Override
public ChannelHandlerContext fireChannelRegistered() {
invokeChannelRegistered(findContextInbound());
return this;
}
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
<div class="se-preview-section-delimiter"></div>
先通过findContextInbound方法,遍历链表,找到当前Context后面第一个inbound类型的Context(在addLast方法中,将Handler封装成Context对象的时候,就已经将inbound赋值,具体看上面),然后再又调用invokeChannelRegistered方法,这时的参数就不是Head了,而是Head后一个节点,这样通过递归的方式往后调用,就形成了事件在Pipeline中的传播。
- 注意:传播靠的是Handler中再调用一次fireChannelXX方法,这个方法会往后找合适的Handler进行传播
为了说明这个问题,我们看下Nettydemo中自定义的Handler
首先是先使用addLast方法添加一个自定义的Handler到pipeline中
ch.pipeline().addLast(new StringDecoder()).addLast(new ServerHandler())
private static class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
String body = (String)msg;
System.out.println("receive body:"+body );
}
}
<div class="se-preview-section-delimiter"></div>
而Handler的逻辑也很简单,就是打印一下接收到的信息,结果很明显,当Client往Server发送了一条消息,控制台就打印
receive body:XXXX
假设,我们有多个自定义的Handler
ch.pipeline().addLast(new StringDecoder()).addLast(new ServerHandler()).addLast(new ServerHandler())
<div class="se-preview-section-delimiter"></div>
如上,我们添加了两个自定义的Handler,那么我们是想事件依次通过两个Handler进行不同的处理(这里两个Handler同样的功能,只为说明问题),那么结果是Client发送一条消息,而Server打印两次
- 结果呢?
结果当然肯定打印了一次啦,不然我写那么多结果和预想一样,不就是在凑字数么=_=….
- 那么怎么样才可以让多个Handler都执行呢?
只需要在Handler最后加一句代码就OK了
ctx.fireChannelRead(msg);
<div class="se-preview-section-delimiter"></div>
这个上面有分析过,会找到下一个对应类型的Context然后调用。
所以我觉得Netty的Pipeline的Inbound传播过程和下图更像
上面例子中,那个问题就是”往后传播”这个步骤漏了。而一些自带的Handler,都会触发这样的步骤,所以添加多个也是可以一路处理到达你的Hadnler
注意:这里有一个问题,假设Context2处理完后继续往后传播,那么就会到了Tail,这会出现一个问题,以ChannelRead为例,看下Tail的实现
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
onUnhandledInboundMessage(msg);
}
protected void onUnhandledInboundMessage(Object msg) {
try {
logger.debug(
"Discarded inbound message {} that reached at the tail of the pipeline. " +
"Please check your pipeline configuration.", msg);
} finally {
ReferenceCountUtil.release(msg);
}
}
<div class="se-preview-section-delimiter"></div>
Netty任务一个事件到达Tail,表示前面Pipeline中没有正确的处理事件,并让其无奈传播到Tail,所以这里打印了一个日志,大概意思大家也懂
writeAndFlush/write与OutBound事件传播的关系
上面讲了InBound的事件传播,InBound事件是IO线程触发的事件,例如read,active等读事件,而OutBound事件是用户自己触发的事件,例如Netty应用中,最常用的就是
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ....
ctx.writeAndFlush(writeBuf);
}
<div class="se-preview-section-delimiter"></div>
或者
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ....
Channel channel = ctx.channel();
channel.writeAndFlush(writeBuf);
}
<div class="se-preview-section-delimiter"></div>
这种write的方式,会触发OutBound事件的传播,下面来说一下是如何传播的,且上面两者write方式触发的事件传播的区别
ctx.writeAndFlush
该方法会调用到AbstractChannelHandlerContext.write(Object, boolean, ChannelPromise)方法,前面的和事件传播无关,暂时不看
private void write(Object msg, boolean flush, ChannelPromise promise) {
AbstractChannelHandlerContext next = findContextOutbound();
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
AbstractWriteTask task;
if (flush) {
task = WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = WriteTask.newInstance(next, m, promise);
}
safeExecute(executor, task, promise, m);
}
}
<div class="se-preview-section-delimiter"></div>
在讲Inbound事件传播机制的时候,说过每次传播会遍历Pipeline中的Handler然后找到Inbound类型进行调用,对于Outbound事件也是类似的,通过findContextOutbound去找到Outbound类型的事件
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while (!ctx.outbound);
return ctx;
}
<div class="se-preview-section-delimiter"></div>
注意这里,从当前的节点开始往前找,这个this是我们自定义的Handler,而之前的例子中,prev只有一个Head。
为了更能说明传播流程,我在Demo中多加了一个Outbound类型的Handler
socketChannel.pipeline()
.addLast(new StringDecoder())
.addLast(new StringEncoder())
.addLast(new NettyServerHandler());
<div class="se-preview-section-delimiter"></div>
这时候,findContextOutbound找到的就是StringEncoder这个Outbound类型的Handler,
然后调用无论走哪个分支,调用AbstractChannelHandlerContext.invokeWrite0(Object, ChannelPromise)方法
private void invokeWrite0(Object msg, ChannelPromise promise) {
try {
((ChannelOutboundHandler) handler()).write(this, msg, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
}
<div class="se-preview-section-delimiter"></div>
由于第一个找到的Outbound类型的Handler是StringEncoder,那么看下其write方法
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
//....
ctx.write(msg, promise);
//....
}
<div class="se-preview-section-delimiter"></div>
这个时候,又调用了write方法,然后又回到AbstractChannelHandlerContext.write(Object, boolean, ChannelPromise)方法,然后继续调用findContextOutbound方法,而这时候的this是StringEncoder,所以找到的是HeadContext,然后再调用HeadContext的write方法,这样形成一个递归的调用,Outbound事件就是这样传播的
Channel.writeAndFlush
看下Channel的writeAndFlush方法,其调用的是AbstractChannel方法
@Override
public ChannelFuture writeAndFlush(Object msg) {
return pipeline.writeAndFlush(msg);
}
<div class="se-preview-section-delimiter"></div>
pipiline的writeAndFlush方法如下:
@Override
public final ChannelFuture write(Object msg) {
return tail.write(msg);
}
总结:直接使用Channel.writeAndFlush会从Tail开始传播,而使用ctx.writeAndFlush则是从当前Handler开始往前传播
服务启动中涉及到的Pipeline的相关知识
看到服务启动分析的文章中,会有一些操作pipeline的代码,可能一开始看的时候不太清楚流程,当分析完pipeline后,这部分的内容也可以充分的了解了