# 前言
虽说[上一节](https://wenjie.store/archives/about-pipeline-2)讲了添加handler的流程,但是[第一节](https://wenjie.store/archives/about-pipeline-1)推导出的理论仍然非常重要,如果对前两节的内容脑海里没有一个印象,建议先回去复习一下。
而本文就来跟进一下删除ChannelHandler的代码逻辑。
> Netty Version:4.1.6
<br/>
# 应用场景
删除ChannelHandler的应用场景显然没有添加ChannelHandler的应用场景多,但是在某些流程中,仍然发挥着很重要的作用,下面就来举两个例子:
- 首次连接时需要一个handler推送消息,推送完之后删除handler。
- 新连接接入时验证发送的密钥,验证完后删除handler。
<br/>
# 大致流程
删除节点的大致流程如下:
- 先根据handler找到要删除的节点,如果存在就返回该节点。
- 将节点从双向链表中删除。
- 回调handlerRemoved事件。
<br/>
# 实验代码
<code>Server.java</code>
```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();
}
}
}
```
<code>AuthHandler.java</code>
```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;
}
}
```
- 可能你会疑问为什么突然冒出个SimpleChannelInboundHandler,前面好像没讲过啊?无需在意,channelRead0只是channelRead的其中一环,之所以覆写channelRead0,是因为channelRead在执行完channelRead0后无需手动传播事件到tail节点进行回收,而是自动回收。
> 关于事件是如何传到tail的,会在下一篇博客写。
<br/>
# 跟进源码
## 寻找节点
至于打断点、telnet连接这些繁琐因人而异的步骤我就不再一一赘述了,下面准备开始跟源码。
先将视角拉到AuthHandler.java的remove方法中,启动Server.java代码,然后telnet连接并随便发送些数据,断点就能来到这个remove方法了,我们跟下去:
此处<span id="tag1">【坐标1】</span>
<code>io.netty.channel.DefaultChannelPipeline#remove(io.netty.channel.ChannelHandler)</code>
```java
@Override
public final ChannelPipeline remove(ChannelHandler handler) {
remove(getContextOrDie(handler));
return this;
}
```
getContextOrDie就是根据handler获取节点的方法,跟进去:
<code>io.netty.channel.DefaultChannelPipeline#getContextOrDie(io.netty.channel.ChannelHandler)</code>
```java
private AbstractChannelHandlerContext getContextOrDie(ChannelHandler handler) {
AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handler);
if (ctx == null) {
throw new NoSuchElementException(handler.getClass().getName());
} else {
return ctx;
}
}
```
- 如果找不到节点则抛出异常。
跟进context方法:
<code>io.netty.channel.DefaultChannelPipeline#context(io.netty.channel.ChannelHandler)</code>
```java
@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;
}
}
```
- 可见其实就是遍历pipeline中存储的双向链表,比对它们的handler,如果是目标handler,则返回当前节点。
<br/>
## 删除节点
现在将视角转回[【坐标1】](#tag1)的remove方法,跟进去:
此处<span id="tag2">【坐标2】</span>
<code>io.netty.channel.DefaultChannelPipeline#remove(io.netty.channel.AbstractChannelHandlerContext)</code>
```java
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;
}
```
- 断言那一行告诉我们是**不允许删除head和tail节点**。
- remove0方法就是删除节点。
- callHandlerRemoved0方法就是回调handlerRemoved事件。
<br/>
这里先跟进remove0方法:
<code>io.netty.channel.DefaultChannelPipeline#remove0</code>
```java
private static void remove0(AbstractChannelHandlerContext ctx) {
AbstractChannelHandlerContext prev = ctx.prev;
AbstractChannelHandlerContext next = ctx.next;
prev.next = next;
next.prev = prev;
}
```
- 从双向链表中删除节点(注意节点本身并没有删除)。
<br/>
## 回调handlerRemoved事件
视角拉回到[【坐标2】](#tag2)的代码,跟进callHandlerRemoved0方法:
<code>io.netty.channel.DefaultChannelPipeline#callHandlerRemoved0</code>
```java
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));
}
}
```
- ctx.handler返回的就是AuthHandler:
虽说上面的是成功回调到handlerRemoved事件了,但由于我们的AuthHandler并没有覆写handlerRemoved方法,所以最后是回调到了ChannelHandler的handlerRemoved方法,这个方法什么都没做:
<code>io.netty.channel.ChannelHandlerAdapter#handlerRemoved</code>
```java
/**
* Do nothing by default, sub-classes may override this method.
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// NOOP
}
```
---
<br/>
# SimpleChannelInboundHandler自动回收
在执行完前面的代码逻辑ctx.pipeline().remove(this)后,由于目标节点仅仅是从双向链表中删除,但是节点对象还并未消失,也就是说有OOM的风险,那是不是就意味着我们还要在channelRead0方法中执行完remove后,还需要手动释放节点 或者 手动把节点传播到tail回收呢?
其实Netty为用户考虑到这一点了,给我们提前写好一些自动处理的逻辑,其中一个就是AuthHandler继承的SimpleChannelInboundHandler。
AuthHandler覆写了一个channelRead0方法,根据前两节的知识,我们都知道,真正被回调的方法应该叫channelRead,所以现在就来看看SimpleChannelInboundHandler的channelRead方法:
<code>io.netty.channel.SimpleChannelInboundHandler#channelRead</code>
```java
@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);
}
}
}
```
- 可见channelRead0方法就是channelRead方法内的其中一步。
- channelRead0就是提供给用户编写自己的逻辑的,等channelRead0执行完后,用户不需要担心对象回收,因为channelRead会继续执行下去,回收当前节点。
---
<br/>
# 小结
- 删除ChannelHandler的源码逻辑还是十分简单的,就是根据handler匹配节点,然后再将节点从双向链表删除。
- 同[上一节](https://wenjie.store/archives/about-pipeline-2)的添加ChannelHandler,在最后都会回调某个事件。
- 为了用户使用更便利,Netty给我们提供了一些实现了含有自动逻辑处理的父类,开发常用的可能就是SimpleChannelInboundHandler了。

【Netty】Pipeline相关(三):删除ChannelHandler