前言
ChannelPipeline数据管道是ChannelHandler数据处理器的容器,负责ChannelHandler的管理和事件的拦截与调度
GitHub地址:https://github.com/RobertoHuang
源码分析
初始化
在Channel初始化时会调用到AbstractChannel的构造方法,Pipeline的初始化动作是在AbstractChannel构造方法中完成的
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
接下来我们继续跟进newChannelPipeline(),探究一下DefaultChannelPipeline底层实现细节
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
在newChannelPipeline()中调用了DefaultChannelPipeline的构造方法,DefaultChannelPipeline构造方法主要完成了三件事。首先将与之关联的Channel保存在属性Channel中,然后实例化了两个对象(一个是TailContext实例tail,一个是HeadContext实例head),然后将head和tail相互指向构成了一个双向链表。数据结构如下图
DefaultChannelPipeline中的每个节点是一个ChannelHandlerContext对象(一个ChannelPipeline中可以有多个ChannelHandler实例,而每一个ChannelHandler实例与ChannelPipeline之间的桥梁就是ChannelHandlerContext实例)
添加节点
我们在初始化bootstrap的时候应该对下面这段代码不陌生,它是将ChannelHandler添加到pipeline的双向链表中去的
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyMessageDecoder(serializer));
socketChannel.pipeline().addLast(new NettyMessageEncoder(serializer));
}
})
下面我们来跟下addLast方法
public final ChannelPipeline addLast(ChannelHandler... handlers) {
return addLast(null, handlers);
}
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
if (handlers == null) {
throw new NullPointerException("handlers");
}
for (ChannelHandler h: handlers) {
if (h == null) {
break;
}
addLast(executor, null, h);
}
return this;
}
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
// 1.禁止非Sharable的handler重复添加到不同的pipeline中
checkMultiplicity(handler);
// 2.创建节点 => DefaultChannelHandlerContext
newCtx = newContext(group, filterName(name, handler), handler);
// 3.添加节点
addLast0(newCtx);
// 如果channel没有与eventloop绑定
// 则创建一个任务 这个任务会在channel被register的时候调用
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
// 4.回调用户方法
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;
}
这里简单地用synchronized方法是为了防止多线程并发操作pipeline底层的双向链表
检查是否有重复Handler
检查是否有重复Handler的实现在checkMultiplicity()方法中
private static void checkMultiplicity(ChannelHandler handler) {
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
if (!h.isSharable() && h.added) {
throw new ChannelPipelineException(h.getClass().getName() + " is not a @Sharable handler, so can't be added or removed multiple times.");
}
h.added = true;
}
}
ChannelHandlerAdapter使用一个成员变量added标识一个Channel是否已经添加过,如果当前要添加的Handler是非共享并且已经添加过那就抛出异常,否则标识该Handler已经添加。由此可见一个Handler如果是Sharable的就可以无限次被添加到Pipeline中,我们客户端代码如果要让一个Handler被共用,只需要加一个@Sharable标注即。而如果Handler是Sharable的一般就通过Spring的注入的方式使用,不需要每次都新建对象
创建节点DefaultChannelHandlerContext
创建节点调用了newContext()方法,代码如下
newCtx = newContext(group, filterName(name, handler), handler);
这里我们需要先分析filterName(name, handler)这段代码,这个方法用于给handler创建一个唯一性的名字
private String filterName(String name, ChannelHandler handler) {
if (name == null) {
// 1.如果传入的name为空 则生成
// Netty生成的name默认为=> 简单类名#0
// 如果简单类名#0已存在则将基数+1 生成name为简单类名#1 以此递增
return generateName(handler);
}
// 2.检查是否有重名 检查通过则返回
checkDuplicateName(name);
return name;
}
检查是否存在重名的Context,如果已存在重名的Context对象则抛出异常
private void checkDuplicateName(String name) {
// 判断是否已有重名的Context
if (context0(name) != null) {
// 如果已存在重名的Context则抛出异常
throw new IllegalArgumentException("Duplicate handler name: " + name);
}
}
private AbstractChannelHandlerContext context0(String name) {
// 从头结点开始遍历所有的Context
// 只要发现某个Context的名字与待添加的name相同 就返回该Context
AbstractChannelHandlerContext context = head.next;
while (context != tail) {
if (context.name().equals(name)) {
return context;
}
context = context.next;
}
return null;
}
在处理完name之后就进入到创建context的过程,代码如下
private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}
private EventExecutor childExecutor(EventExecutorGroup group) {
if (group == null) {
return null;
}
...
}
由前面的调用链得知group为null,因此此处的childExecutor(group)也返回null
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;
}
在DefaultChannelHandlerContext构造方法中标记了Handler是否是Inboud类型或OutBound类型并调用了父类的构造方法,最后保存了Handler的引用,判断Handler是Inbound还是OutBound的代码如下
private static boolean isInbound(ChannelHandler handler) {
return handler instanceof ChannelInboundHandler;MessageToMessageCodec
}
private static boolean isOutbound(ChannelHandler handler) {
return handler instanceof ChannelOutboundHandler;
}
调用父类构造函数,初始化成员变量
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name, boolean inbound, boolean outbound) {
this.name = ObjectUtil.checkNotNull(name, "name");
this.pipeline = pipeline;
// 此处exeutor为null
this.executor = executor;
this.inbound = inbound;
this.outbound = outbound;
// 初次ordered为true
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
如果一个Handler实现了两类接口,那么它既是一个inBound类型的Handler,又是一个outBound类型的Handler,如下图
常用的将decode操作和encode操作合并到一起的codec,一般会继承MessageToMessageCodec,而MessageToMessageCodec就是继承ChannelDuplexHandler
添加Context节点到Pipeline的双向链表中
在创建完Context之后调用addLast0()将Context添加至Pipeline的双向链表中
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
添加节点其实就是个双向链表的插入操作,具体步骤如下图所示
操作完毕,该Context就加入到Pipeline中
至此Pipeline添加节点的操作就完成了,其他的addXXX方法原理类似
Context节点添加完毕回调Handler方法
回调用户方法逻辑在callHandlerAdded0()方法中完成,代码如下
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
try {
ctx.handler().handlerAdded(ctx);
ctx.setAddComplete();
} catch (Throwable t) {
...
}
}
Handler可以重写handlerAdded方法在Handler被添加成功后 执行部分操作
public class DemoHandler extends SimpleChannelInboundHandler<...> {
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// 节点被添加完毕之后回调到此
}
}
接下来,设置该节点的状态
final void setAddComplete() {
for (;;) {
int oldState = handlerState;
if (oldState == REMOVE_COMPLETE || HANDLER_STATE_UPDATER.compareAndSet(this, oldState, ADD_COMPLETE)) {
return;
}
}
}
用cas修改节点的状态至REMOVE_COMPLETE(说明该节点已经被移除)或者ADD_COMPLETE(说明节点已被添加)
删除节点
Netty有个最大的特性之一就是Handler可插拔做到动态编织Pipeline,比如在首次建立连接的时候需要通过进行权限认证,在认证通过之后就可以将此Context移除,下次Pipeline在传播事件的时候就就不会调用到权限认证处理器
下面是权限认证Handler最简单的实现,第一个数据包传来的是认证信息,如果校验通过就删除此Handler,否则直接关闭连接
public class AuthHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf data) throws Exception {
if (verify(authDataPacket)) {
ctx.pipeline().remove(this);
} else {
ctx.close();
}
}
private boolean verify(ByteBuf byteBuf) {
// ...
}
}
重点就在ctx.pipeline().remove(this)这段代码
public final ChannelPipeline remove(ChannelHandler handler) {
remove(getContextOrDie(handler));
return this;
}
remove操作可以分为如下三个步骤
1.找到待删除的节点
2.调整双向链表指针删除节点
3.Context节点删除完毕回调Handler方法
找到待删除的节点
private AbstractChannelHandlerContext getContextOrDie(String name) {
AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(name);
if (ctx == null) {
throw new NoSuchElementException(name);
} else {
return ctx;
}
}
public final ChannelHandlerContext context(ChannelHandler handler) {
if (handler == null) {
throw new NullPointerException("handler");
}
AbstractChannelHandlerContext ctx = head.next;
for (;;) {
if (ctx == null) {
return null;
}
if (ctx.handler() == handler) {
return ctx;
}
ctx = ctx.next;
}
}
通过遍历链表方式根据Handler找到对应的Context节点(判断依据 => Context的Handler和当前Handler相同)
调整双向链表指针删除节点
private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
// 头结点和为节点不能删除
assert ctx != head && ctx != tail;
synchronized (this) {
// 调整双向链表指针删除节点
remove0(ctx);
// 如果channel没有与eventloop绑定
// 则创建一个任务 这个任务会在channel被register的时候调用
if (!registered) {
callHandlerCallbackLater(ctx, false);
return ctx;
}
// 回调用户函数
EventExecutor executor = ctx.executor();
if (!executor.inEventLoop()) {
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerRemoved0(ctx);
}
});
return ctx;
}
}
callHandlerRemoved0(ctx);
return ctx;
}
删除节点是通过remove0方法实现的,代码如下
private static void remove0(AbstractChannelHandlerContext ctx) {
AbstractChannelHandlerContext prev = ctx.prev;
AbstractChannelHandlerContext next = ctx.next;
prev.next = next;
next.prev = prev;
}
删除节点其实就是个双向链表的删除操作,具体步骤如下图所示
操作完毕,该Context就从Pipeline中移除
Context节点删除完毕回调Handler方法
回调用户方法逻辑在callHandlerRemoved0()方法中完成,代码如下
private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {
try {
try {
ctx.handler().handlerRemoved(ctx);
} finally {
ctx.setRemoved();
}
} catch (Throwable t) {
fireExceptionCaught(new ChannelPipelineException(ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t));
}
}
Handler可以重写handlerRemoved方法在Handler被删除成功后 执行部分操作
public class DemoHandler extends SimpleChannelInboundHandler<...> {
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// 节点被删除完毕之后回调到此
}
}
最后将该节点的状态设置为removed
final void setRemoved() {
handlerState = REMOVE_COMPLETE;
}
至此Pipeline添加节点的删除就完成了,其他的removeXXX方法原理类似