虽说上一节讲了添加handler的流程,但是第一节推导出的理论仍然非常重要,如果对前两节的内容脑海里没有一个印象,建议先回去复习一下。
而本文就来跟进一下删除ChannelHandler的代码逻辑。
Netty Version:4.1.6
删除ChannelHandler的应用场景显然没有添加ChannelHandler的应用场景多,但是在某些流程中,仍然发挥着很重要的作用,下面就来举两个例子:
删除节点的大致流程如下:
Server.java
import com.imooc.netty.ch3.ServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.AttributeKey;
public final class Server {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(1);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new AuthHandler());
}
});
ChannelFuture f = b.bind(8888).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
AuthHandler.java
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class AuthHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
if (!paas(msg)) {
ctx.pipeline().remove(this);
} else {
// do something
}
}
/**
* 模拟验证不通过
*/
private boolean paas(ByteBuf password) {
return false;
}
}
关于事件是如何传到tail的,会在下一篇博客写。
至于打断点、telnet连接这些繁琐因人而异的步骤我就不再一一赘述了,下面准备开始跟源码。
先将视角拉到AuthHandler.java的remove方法中,启动Server.java代码,然后telnet连接并随便发送些数据,断点就能来到这个remove方法了,我们跟下去:
此处【坐标1】
io.netty.channel.DefaultChannelPipeline#remove(io.netty.channel.ChannelHandler)
@Override
public final ChannelPipeline remove(ChannelHandler handler) {
remove(getContextOrDie(handler));
return this;
}
getContextOrDie就是根据handler获取节点的方法,跟进去:
io.netty.channel.DefaultChannelPipeline#getContextOrDie(io.netty.channel.ChannelHandler)
private AbstractChannelHandlerContext getContextOrDie(ChannelHandler handler) {
AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handler);
if (ctx == null) {
throw new NoSuchElementException(handler.getClass().getName());
} else {
return ctx;
}
}
跟进context方法:
io.netty.channel.DefaultChannelPipeline#context(io.netty.channel.ChannelHandler)
@Override
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;
}
}
现在将视角转回【坐标1】的remove方法,跟进去:
此处【坐标2】
io.netty.channel.DefaultChannelPipeline#remove(io.netty.channel.AbstractChannelHandlerContext)
private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
assert ctx != head && ctx != tail;
synchronized (this) {
remove0(ctx);
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方法:
io.netty.channel.DefaultChannelPipeline#remove0
private static void remove0(AbstractChannelHandlerContext ctx) {
AbstractChannelHandlerContext prev = ctx.prev;
AbstractChannelHandlerContext next = ctx.next;
prev.next = next;
next.prev = prev;
}
视角拉回到【坐标2】的代码,跟进callHandlerRemoved0方法:
io.netty.channel.DefaultChannelPipeline#callHandlerRemoved0
private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {
// Notify the complete removal.
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));
}
}
虽说上面的是成功回调到handlerRemoved事件了,但由于我们的AuthHandler并没有覆写handlerRemoved方法,所以最后是回调到了ChannelHandler的handlerRemoved方法,这个方法什么都没做:
io.netty.channel.ChannelHandlerAdapter#handlerRemoved
/**
* Do nothing by default, sub-classes may override this method.
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// NOOP
}
在执行完前面的代码逻辑ctx.pipeline().remove(this)后,由于目标节点仅仅是从双向链表中删除,但是节点对象还并未消失,也就是说有OOM的风险,那是不是就意味着我们还要在channelRead0方法中执行完remove后,还需要手动释放节点 或者 手动把节点传播到tail回收呢?
其实Netty为用户考虑到这一点了,给我们提前写好一些自动处理的逻辑,其中一个就是AuthHandler继承的SimpleChannelInboundHandler。
AuthHandler覆写了一个channelRead0方法,根据前两节的知识,我们都知道,真正被回调的方法应该叫channelRead,所以现在就来看看SimpleChannelInboundHandler的channelRead方法:
io.netty.channel.SimpleChannelInboundHandler#channelRead
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
channelRead0(ctx, imsg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}