(*文章基于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
ChannelHandler和ChannelHandlerContext
ChannelHandler
先看下ChannelHandler接口的定义
public interface ChannelHandler {
// handler被添加进pipeline的时候的回调方法
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
// handler从pipeline中移除的时候的回调方法
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
}
另外两个常用的接口如下,这两个接口定义了Netty的两种事件流:Inbound和Outbound
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;
}
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;
}
接口不做解释,从方法名称可以知道每个方法大概关联的操作,另外有一点比较重要的是,ChannelOutboundHandler和ChannelInboundHandler两个划分两种事件流,而每个方法就代表了每种事件流下的事件,举个例子来说,用户调用的write、flush或者connect等都属于Outbound事件,而且Outbound一般都是用户触发
ChannelHandlerContext
接来下看下ChannelHandlerContext接口的核心方法
public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {
// 返回对应的Channel对象
Channel channel();
// 返回对应的EventLoop对象
EventExecutor executor();
// 返回对应的ChannelHandler对象
ChannelHandler handler();
// 该Context对应的Handler是否从pipeline中移除
boolean isRemoved();
/*******************以下的fire方法都是触发对应事件在pipeline中传播*********************/
ChannelHandlerContext fireChannelRegistered();
ChannelHandlerContext fireChannelUnregistered();
ChannelHandlerContext fireChannelActive();
ChannelHandlerContext fireChannelInactive();
ChannelHandlerContext fireExceptionCaught(Throwable cause);
ChannelHandlerContext fireUserEventTriggered(Object evt);
ChannelHandlerContext fireChannelRead(Object msg);
ChannelHandlerContext fireChannelReadComplete();
ChannelHandlerContext fireChannelWritabilityChanged();
ChannelHandlerContext read();
ChannelHandlerContext flush();
// 返回对应的pipeline
ChannelPipeline pipeline();
}
从方法中可以看出,Context和Pipeline、Handler、Channel、EventLoop是一对一的关系。
方法定义中有很多fire方法,是触发事件传播的入口,那么也可以看出Context是一个触发事件传播的结构
看下Pipeline里使用的DefaultChannelHandlerContext及其父类
final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {
private final ChannelHandler handler;
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;
}
// 返回封装的Handler
public ChannelHandler handler() {
return handler;
}
// 通过handler继承的类的类型判断是Inbound还是Outbound
private static boolean isInbound(ChannelHandler handler) {
return handler instanceof ChannelInboundHandler;
}
// 通过handler继承的类的类型判断是Inbound还是Outbound
private static boolean isOutbound(ChannelHandler handler) {
return handler instanceof ChannelOutboundHandler;
}
}
父类代码如下,只贴了部分代码
abstract class AbstractChannelHandlerContext extends DefaultAttributeMap
implements ChannelHandlerContext, ResourceLeakHint {
// Context是pipeline中的一个节点,是双向链表,这里保存了两个指针类型便于向前和向后遍历
volatile AbstractChannelHandlerContext next;
volatile AbstractChannelHandlerContext prev;
private static final AtomicIntegerFieldUpdater HANDLER_STATE_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(AbstractChannelHandlerContext.class, "handlerState");
//当handlerAdded方法被调用的方式不是马上调用的时候,会设置为该状态
// 例如pipeline中调用callHandlerCallbackLater或者在EventLoop的execute方法中执行
// 这种情况是延迟执行,或者说不是马上执行,需要有个中间状态
private static final int ADD_PENDING = 1;
// handlerAdded调用前设置的状态
private static final int ADD_COMPLETE = 2;
// 当节点被移除之后设置的状态
private static final int REMOVE_COMPLETE = 3;
// 初始状态
private static final int INIT = 0;
// handler的类型
private final boolean inbound;
private final boolean outbound;
private final DefaultChannelPipeline pipeline;
private final String name;
private final boolean ordered;
// 当channelReadComplete、read、channelWritableStateChanged、flush4个四个事件发生的时候
// 如果不在EventLoop的线程中,那么会转换成Runnable对象放到EventLoop线程中处理
private Runnable invokeChannelReadCompleteTask;
private Runnable invokeReadTask;
private Runnable invokeChannelWritableStateChangedTask;
private Runnable invokeFlushTask;
private volatile int handlerState = INIT;
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;
// Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
@Override
public Channel channel() {
return pipeline.channel();
}
// 返回pipeline
public ChannelPipeline pipeline() {
return pipeline;
}
final EventExecutor executor;
// 一般调用pipeline添加context的时候都没传这个参数,所以为空
// 如果为空,则获取Channel的EventLoop
// 而Channel和EventLoop绑定是在注册时候,也就是说,在注册完成前是该返回是空
public EventExecutor executor() {
if (executor == null) {
return channel().eventLoop();
} else {
return executor;
}
}
}
ChannelPipeline如何与ChannelHandler关联
先看下ChannelPipeline接口的部分定义
public interface ChannelPipeline
extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable> {
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);
// 下面会触发handlerAdded方法,根据不同情况判断该以什么方式调用
// 未注册的话走Callback流程
if (!registered) {
// 将状态设置成ADD_PENDING
// 然后handlerAdded的调用将换成callback
// 等注册完的时候会调用callback,然后调用handlerAdded方法
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
// 如果和EventLoop不是同个线程
if (!executor.inEventLoop()) {
newCtx.setAddPending();
// 将handlerAdded的调用放到队列,等待EventLoop线程执行
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
// 如果是在EventLoop线程中,那么直接执行
callHandlerAdded0(newCtx);
return this;
}
newContext主要创建了一个DefaultChannelHandlerContext对象,构造方法之前已经描述过了。
callHandlerAdded0方法主要是将Context的状态设置为ADD_COMPLETE和调用handlerAdded方法
addLast0方法的实现:
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
逻辑很简单,将Context加入到Tail前面,链表的相关知识,不再分析引用的变化过程。
callHandlerCallbackLater方法如下:
private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
assert !registered;
// PendingHandlerAddedTask是调用callHandlerAdded0方法
// PendingHandlerRemovedTask是调用callHandlerRemoved0方法
PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
PendingHandlerCallback pending = pendingHandlerCallbackHead;
// 方法链表中等待后续遍历调用
if (pending == null) {
pendingHandlerCallbackHead = task;
} else {
while (pending.next != null) {
pending = pending.next;
}
pending.next = task;
}
}
其他方法也是类似的过程,此处省略
InBound事件在Pipeline中传播
以注册事件为例,在之前讲过,注册调用的是AbstractUnsafe的register0方法,其中有句代码如下:
pipeline.fireChannelRegistered();
在这里就触发了事件的传播,看下其实现
public final ChannelPipeline fireChannelRegistered() {
AbstractChannelHandlerContext.invokeChannelRegistered(head);
return this;
}
直接调用了静态方法,传入Head,表示从Head开始传播
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中)
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
这里又调回了HeadContext的channelRegistered方法
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
invokeHandlerAddedIfNeeded();
ctx.fireChannelRegistered();
}
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;
}
先通过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 );
}
}
而Handler的逻辑也很简单,就是打印一下接收到的信息,结果很明显,当Client往Server发送了一条消息,控制台就打印
receive body:XXXX
假设,我们有多个自定义的Handler
ch.pipeline().addLast(new StringDecoder())
.addLast(new ServerHandler()).addLast(new ServerHandler())
如上,我们添加了两个自定义的Handler,那么我们是想事件依次通过两个Handler进行不同的处理(这里两个Handler同样的功能,只为说明问题),那么结果是Client发送一条消息,而Server打印两次
- 结果呢?
结果当然肯定打印了一次啦,不然我写那么多结果和预想一样,不就是在凑字数么=_=....
- 那么怎么样才可以让多个Handler都执行呢?
只需要在Handler最后加一句代码就OK了
ctx.fireChannelRead(msg);
这个上面有分析过,会找到下一个对应类型的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);
}
}
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);
}
或者
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ....
Channel channel = ctx.channel();
channel.writeAndFlush(writeBuf);
}
这种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);
}
}
在讲Inbound事件传播机制的时候,说过每次传播会遍历Pipeline中的Handler然后找到Inbound类型进行调用,对于Outbound事件也是类似的,通过findContextOutbound去找到Outbound类型的事件
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while (!ctx.outbound);
return ctx;
}
注意这里,从当前的节点开始往前找,这个this是我们自定义的Handler,而之前的例子中,prev只有一个Head。
为了更能说明传播流程,我在Demo中多加了一个Outbound类型的Handler
socketChannel.pipeline()
.addLast(new StringDecoder())
.addLast(new StringEncoder())
.addLast(new NettyServerHandler());
这时候,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);
}
}
由于第一个找到的Outbound类型的Handler是StringEncoder,那么看下其write方法
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
//....
ctx.write(msg, promise);
//....
}
这个时候,又调用了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);
}
pipiline的writeAndFlush方法如下:
@Override
public final ChannelFuture write(Object msg) {
return tail.write(msg);
}
总结:直接使用Channel.writeAndFlush会从Tail开始传播,而使用ctx.writeAndFlush则是从当前Handler开始往前传播
服务启动中涉及到的Pipeline的相关知识
看到服务启动分析的文章中,会有一些操作pipeline的代码,可能一开始看的时候不太清楚流程,当分析完pipeline后,这部分的内容也可以充分的了解了
总结
一个Channel在创建的时候就会创建一个对应的ChannelPipeline,
通过上面的分析,可以看到ChannelPipeline的设计是线程安全的,有很多地方的操作就是为了这个线程安全做了很多的操作,例如addLast调用handlerAdded会转换成EventLoop队列任务,Netty中很多地方都是类似的,为了避免这种多线程操作的问题都是先转成队列的任务,从而转换成单线程的操作,这种设计需要好好琢磨