netty 默认Channel管道线-通道处理器移除与替换

本文详细解析了Netty中管道线处理器的移除与替换过程,包括通过名称、处理器句柄或类型进行操作的具体实现。重点介绍了如何移除处理器上下文并触发相关事件,以及替换处理器时新旧处理器的添加与移除顺序。
netty Inboudn/Outbound通道Invoker:[url]http://donald-draper.iteye.com/blog/2388233[/url]
netty 异步任务-ChannelFuture:[url]http://donald-draper.iteye.com/blog/2388297[/url]
netty 管道线定义-ChannelPipeline:[url]http://donald-draper.iteye.com/blog/2388453[/url]
netty 默认Channel管道线初始化:[url]http://donald-draper.iteye.com/blog/2388613[/url]
netty 默认Channel管道线-添加通道处理器:[url]http://donald-draper.iteye.com/blog/2388726[/url]
[size=medium][b]引言:[/b][/size]
上一篇文章我们看了Channel管道线添加通道处理器的过程,先来回顾一下:
添加通道处理器到管道头部,首次检查通道处理器是否为共享模式,如果非共享,且已添加,则抛出异常;检查通道处理器名在管道内,是否存在对应通道处理器上下文,已存在抛出异常;根据事件执行器,处理器名,及处理器,构造处理器上下文;添加处理器上限文到管道上下文链头;如果通道没有注册到事件循环,上下文添加到管道时,创建添加通道处理器回调任务,并将任务添加管道的回调任务链中,当通道注册到事件循环时,触发通道处理器的handlerAdded事件,已注册则创建一个线程,用于调用通道处理器的handlerAdded事件方法,及更新上下文状态为已添加,并交由事件执行器执行;最好调用callHandlerAdded0方法,确保调用通道处理器的handlerAdded事件方法,更新上下文状态为已添加。其他last(添加到管道尾部),before(添加指定上下文的前面),after(添加指定上下文的后面)操作,基本上与addfirst思路基本相同,不同的是添加到管道上下文链的位置。
今天来看一下移除通道处理器:
移除指定类型的通道处理器
@Override
public final ChannelPipeline remove(ChannelHandler handler) {
//先获取通道处理器对应的上下文,在委托给上下文移除方法
remove(getContextOrDie(handler));
return this;
}
//获取处理器对应的上下文
private AbstractChannelHandlerContext getContextOrDie(ChannelHandler handler) {
//实际获取上下文方法
AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handler);
if (ctx == null) {
throw new NoSuchElementException(handler.getClass().getName());
} else {
return ctx;
}
}

@Override
public final ChannelHandlerContext context(ChannelHandler handler) {
if (handler == null) {
throw new NullPointerException("handler");
}
AbstractChannelHandlerContext ctx = head.next;
for (;;) {
if (ctx == null) {
return null;
}
//上下文处理器句柄与handler相同,则返回
if (ctx.handler() == handler) {
return ctx;
}
ctx = ctx.next;
}
}




//根据名字移除通道处理
@Override
public final ChannelHandler remove(String name) {
return remove(getContextOrDie(name)).handler();
}

private AbstractChannelHandlerContext getContextOrDie(String name) {
AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(name);
if (ctx == null) {
throw new NoSuchElementException(name);
} else {
return ctx;
}
}

@Override
public final ChannelHandlerContext context(String name) {
if (name == null) {
throw new NullPointerException("name");
}
return context0(name);
}

private AbstractChannelHandlerContext context0(String name) {
AbstractChannelHandlerContext context = head.next;
while (context != tail) {
//上下文名与指定的处理器名相同的返回上下文
if (context.name().equals(name)) {
return context;
}
context = context.next;
}
return null;
}


@SuppressWarnings("unchecked")
//移除指定参数类型处理器
@Override
public final <T extends ChannelHandler> T remove(Class<T> handlerType) {
return (T) remove(getContextOrDie(handlerType)).handler();
}


private AbstractChannelHandlerContext getContextOrDie(Class<? extends ChannelHandler> handlerType) {
AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handlerType);
if (ctx == null) {
throw new NoSuchElementException(handlerType.getName());
} else {
return ctx;
}
}

@Override
public final ChannelHandlerContext context(Class<? extends ChannelHandler> handlerType) {
if (handlerType == null) {
throw new NullPointerException("handlerType");
}

AbstractChannelHandlerContext ctx = head.next;
for (;;) {
if (ctx == null) {
return null;
}
//如果上下文关联的处理器类型为handlerType,则返回上下文
if (handlerType.isAssignableFrom(ctx.handler().getClass())) {
return ctx;
}
ctx = ctx.next;
}
}

上面的方法没有什么好说的,很简单。
从上面可以看出无论是,无论是根据名称,处理器句柄,还是根据类型,都是首先获取对应的
处理器上下文,
private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
//断言移除的上下文既不是头部也不是尾部
assert ctx != head && ctx != tail;
synchronized (this) {
//移除上下文
remove0(ctx);
// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we remove the context from the pipeline and add a task that will call
// ChannelHandler.handlerRemoved(...) once the channel is registered.
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;
}

移除通道上下文方法,有几点要关注
1.
//移除上下文
remove0(ctx);

private static void remove0(AbstractChannelHandlerContext ctx) {
AbstractChannelHandlerContext prev = ctx.prev;
AbstractChannelHandlerContext next = ctx.next;
prev.next = next;
next.prev = prev;
}

2.
if (!registered) {
//如果通道已经从事件循环反注册,则添加移除回调任务
callHandlerCallbackLater(ctx, false);
return ctx;
}

//添加移除上下文回调任务到,管道回调任务链
private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
assert !registered;
PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
PendingHandlerCallback pending = pendingHandlerCallbackHead;
if (pending == null) {
pendingHandlerCallbackHead = task;
} else {
// Find the tail of the linked-list.
while (pending.next != null) {
pending = pending.next;
}
pending.next = task;
}
}

private final class PendingHandlerRemovedTask extends PendingHandlerCallback {

PendingHandlerRemovedTask(AbstractChannelHandlerContext ctx) {
super(ctx);
}

@Override
public void run() {
//触发处理移除事件,并更新上下为状态为已移除状态
callHandlerRemoved0(ctx);
}

@Override
void execute() {
EventExecutor executor = ctx.executor();
if (executor.inEventLoop()) {
callHandlerRemoved0(ctx);
} else {
//如果移除线程不在当前事务循环中,移除任务交给上下文关联的事件执行器
try {
executor.execute(this);
} catch (RejectedExecutionException e) {
if (logger.isWarnEnabled()) {
logger.warn(
"Can't invoke handlerRemoved() as the EventExecutor {} rejected it," +
" removing handler {}.", executor, ctx.name(), e);
}
// remove0(...) was call before so just call AbstractChannelHandlerContext.setRemoved().
ctx.setRemoved();
}
}
}
}

3.
//触发处理移除事件,并更新上下为状态为已移除
private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {
// Notify the complete removal.
try {
try {
//触发上下文关联的处理器handlerRemoved事件
ctx.handler().handlerRemoved(ctx);
} finally {
//更新上下文状态为已移除
ctx.setRemoved();
}
} catch (Throwable t) {
fireExceptionCaught(new ChannelPipelineException(
ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t));
}
}

从上面可看出,实际移除通道处理器上下文为,首先从管道中移除对应的上下文,如果通道已经从事件循环反注册,
则添加移除回调任务到管道回调任务链,否则直接创建线程(触发上下文关联的处理器handlerRemoved事件,
更新上下文状态为已移除),有上下文关联的事件执行器执行。
有了上面的铺垫,下面两个方法应该很好理解
//移除管道头部的处理器
@Override
public final ChannelHandler removeFirst() {
if (head.next == tail) {
throw new NoSuchElementException();
}
return remove(head.next).handler();
}
移除管道尾部的处理器
@Override
public final ChannelHandler removeLast() {
if (head.next == tail) {
throw new NoSuchElementException();
}
return remove(tail.prev).handler();
}

上述中的头部与尾部非为傀儡节点,为实际上下文的第一个和最后一个元素。
再来看一下替换通道处理器操作操作:
@Override
public final ChannelPipeline replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler) {
replace(getContextOrDie(oldHandler), newName, newHandler);
return this;
}
@Override
public final ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler) {
return replace(getContextOrDie(oldName), newName, newHandler);
}
@Override
@SuppressWarnings("unchecked")
public final <T extends ChannelHandler> T replace(
Class<T> oldHandlerType, String newName, ChannelHandler newHandler) {
return (T) replace(getContextOrDie(oldHandlerType), newName, newHandler);
}

从上可以看无论是根据名称,处理器句柄,还是根据类型替换通道处理器,都是首先获取对应的处理器上下文,然后委托给replace(final AbstractChannelHandlerContext ctx, String newName, ChannelHandler newHandler)
方法。

private ChannelHandler replace(
final AbstractChannelHandlerContext ctx, String newName, ChannelHandler newHandler) {
assert ctx != head && ctx != tail;
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(newHandler);
if (newName == null) {
newName = generateName(newHandler);
} else {
boolean sameName = ctx.name().equals(newName);
if (!sameName) {
checkDuplicateName(newName);
}
}
//创建新处理器的上下文
newCtx = newContext(ctx.executor, newName, newHandler);
//用新的处理上下文,替换管道上下文链中原始旧的上下文
replace0(ctx, newCtx);

// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we replace the context in the pipeline
// and add a task that will call ChannelHandler.handlerAdded(...) and
// ChannelHandler.handlerRemoved(...) once the channel is registered.
if (!registered) {
//添加新上下文的添加回调任务
callHandlerCallbackLater(newCtx, true);
//添加旧上下文的移除回调任务
callHandlerCallbackLater(ctx, false);
return ctx.handler();
}
EventExecutor executor = ctx.executor();
if (!executor.inEventLoop()) {
executor.execute(new Runnable() {
//如果
@Override
public void run() {
//先添加,后移除,因为移除操作会触发channelRead和flush事件,而这些事件处理必须在handlerAdded事件后
// Invoke newHandler.handlerAdded() first (i.e. before oldHandler.handlerRemoved() is invoked)
// because callHandlerRemoved() will trigger channelRead() or flush() on newHandler and
// those event handlers must be called after handlerAdded().
//更新新上下文对应的处理器的handlerAdded,并更新,新上下状态为已添加
callHandlerAdded0(newCtx);
//更新旧上下文对应的处理器的handlerRemoved,并更新,新旧上下状态为已添加
callHandlerRemoved0(ctx);
}
});
return ctx.handler();
}
}
// Invoke newHandler.handlerAdded() first (i.e. before oldHandler.handlerRemoved() is invoked)
// because callHandlerRemoved() will trigger channelRead() or flush() on newHandler and those
// event handlers must be called after handlerAdded().
callHandlerAdded0(newCtx);
callHandlerRemoved0(ctx);
return ctx.handler();
}
//替换上下文
private static void replace0(AbstractChannelHandlerContext oldCtx, AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = oldCtx.prev;
AbstractChannelHandlerContext next = oldCtx.next;
newCtx.prev = prev;
newCtx.next = next;

// Finish the replacement of oldCtx with newCtx in the linked list.
// Note that this doesn't mean events will be sent to the new handler immediately
// because we are currently at the event handler thread and no more than one handler methods can be invoked
// at the same time (we ensured that in replace().)
prev.next = newCtx;
next.prev = newCtx;

// update the reference to the replacement so forward of buffered content will work correctly
//主要原始上下文的前驱和后继同时指向新上下文,以便转发剩余的buf内容
oldCtx.prev = newCtx;
oldCtx.next = newCtx;
}

从上面可以看出,其实替换上下文,就是添加新上下文到管道中原始上下文的位置,
并将原始上下文的前驱和后继同时指向新上下文,以便转发剩余的buf内容。
可以简单理解为添加新上下文,移除原始上下文,注意必须先添加,后移除,
因为移除操作会触发channelRead和flush事件,而这些事件处理必须在handlerAdded事件后

[size=medium][b]总结:[/b][/size]
[color=blue]
无论是根据名称,处理器句柄,还是根据类型移除通道处理器,都是首先获取对应的处理器上下文,从管道中移除对应的上下文,如果通道已经从事件循环反注册,则添加移除回调任务到管道回调任务链,否则直接创建线程(触发上下文关联的处理器handlerRemoved事件,更新上下文状态为已移除),有上下文关联的事件执行器执行。
无论是根据名称,处理器句柄,还是根据类型替换通道处理器,都是首先获取对应的
处理器上下文,然后添加新上下文到管道中原始上下文的位置,并将原始上下文的前驱和后继同时指向新上下文,以便转发剩余的buf内容;可以简单理解为添加新上下文,移除原始上下文,注意必须先添加,后移除,因为移除操作会触发channelRead和flush事件,而这些事件处理必须在handlerAdded事件后[/color]。
按你说的之后C:\Users\admin\.jdks\jdk-17.0.12+7\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2024.3.6\lib\idea_rt.jar=52853" -Dfile.encoding=UTF-8 -classpath D:\UserData\Desktop\Netty和gRPC\Netty_TEST\target\classes;D:\r\io\netty\netty-all\4.1.86.Final\netty-all-4.1.86.Final.jar;D:\r\io\netty\netty-buffer\4.1.86.Final\netty-buffer-4.1.86.Final.jar;D:\r\io\netty\netty-codec\4.1.86.Final\netty-codec-4.1.86.Final.jar;D:\r\io\netty\netty-codec-dns\4.1.86.Final\netty-codec-dns-4.1.86.Final.jar;D:\r\io\netty\netty-codec-haproxy\4.1.86.Final\netty-codec-haproxy-4.1.86.Final.jar;D:\r\io\netty\netty-codec-http\4.1.86.Final\netty-codec-http-4.1.86.Final.jar;D:\r\io\netty\netty-codec-http2\4.1.86.Final\netty-codec-http2-4.1.86.Final.jar;D:\r\io\netty\netty-codec-memcache\4.1.86.Final\netty-codec-memcache-4.1.86.Final.jar;D:\r\io\netty\netty-codec-mqtt\4.1.86.Final\netty-codec-mqtt-4.1.86.Final.jar;D:\r\io\netty\netty-codec-redis\4.1.86.Final\netty-codec-redis-4.1.86.Final.jar;D:\r\io\netty\netty-codec-smtp\4.1.86.Final\netty-codec-smtp-4.1.86.Final.jar;D:\r\io\netty\netty-codec-socks\4.1.86.Final\netty-codec-socks-4.1.86.Final.jar;D:\r\io\netty\netty-codec-stomp\4.1.86.Final\netty-codec-stomp-4.1.86.Final.jar;D:\r\io\netty\netty-codec-xml\4.1.86.Final\netty-codec-xml-4.1.86.Final.jar;D:\r\io\netty\netty-common\4.1.86.Final\netty-common-4.1.86.Final.jar;D:\r\io\netty\netty-handler\4.1.86.Final\netty-handler-4.1.86.Final.jar;D:\r\io\netty\netty-transport-native-unix-common\4.1.86.Final\netty-transport-native-unix-common-4.1.86.Final.jar;D:\r\io\netty\netty-handler-proxy\4.1.86.Final\netty-handler-proxy-4.1.86.Final.jar;D:\r\io\netty\netty-handler-ssl-ocsp\4.1.86.Final\netty-handler-ssl-ocsp-4.1.86.Final.jar;D:\r\io\netty\netty-resolver\4.1.86.Final\netty-resolver-4.1.86.Final.jar;D:\r\io\netty\netty-resolver-dns\4.1.86.Final\netty-resolver-dns-4.1.86.Final.jar;D:\r\io\netty\netty-transport\4.1.86.Final\netty-transport-4.1.86.Final.jar;D:\r\io\netty\netty-transport-rxtx\4.1.86.Final\netty-transport-rxtx-4.1.86.Final.jar;D:\r\io\netty\netty-transport-sctp\4.1.86.Final\netty-transport-sctp-4.1.86.Final.jar;D:\r\io\netty\netty-transport-udt\4.1.86.Final\netty-transport-udt-4.1.86.Final.jar;D:\r\io\netty\netty-transport-classes-epoll\4.1.86.Final\netty-transport-classes-epoll-4.1.86.Final.jar;D:\r\io\netty\netty-transport-classes-kqueue\4.1.86.Final\netty-transport-classes-kqueue-4.1.86.Final.jar;D:\r\io\netty\netty-resolver-dns-classes-macos\4.1.86.Final\netty-resolver-dns-classes-macos-4.1.86.Final.jar;D:\r\io\netty\netty-transport-native-epoll\4.1.86.Final\netty-transport-native-epoll-4.1.86.Final-linux-x86_64.jar;D:\r\io\netty\netty-transport-native-epoll\4.1.86.Final\netty-transport-native-epoll-4.1.86.Final-linux-aarch_64.jar;D:\r\io\netty\netty-transport-native-kqueue\4.1.86.Final\netty-transport-native-kqueue-4.1.86.Final-osx-x86_64.jar;D:\r\io\netty\netty-transport-native-kqueue\4.1.86.Final\netty-transport-native-kqueue-4.1.86.Final-osx-aarch_64.jar;D:\r\io\netty\netty-resolver-dns-native-macos\4.1.86.Final\netty-resolver-dns-native-macos-4.1.86.Final-osx-x86_64.jar;D:\r\io\netty\netty-resolver-dns-native-macos\4.1.86.Final\netty-resolver-dns-native-macos-4.1.86.Final-osx-aarch_64.jar;D:\r\com\fasterxml\jackson\core\jackson-databind\2.13.0\jackson-databind-2.13.0.jar;D:\r\com\fasterxml\jackson\core\jackson-annotations\2.13.0\jackson-annotations-2.13.0.jar;D:\r\com\fasterxml\jackson\core\jackson-core\2.13.0\jackson-core-2.13.0.jar org.example.GreetServer 8月 25, 2025 5:29:14 下午 io.netty.channel.DefaultChannelPipeline onUnhandledInboundException 警告: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception. com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 0)): only regular white space (\r, \n, \t) is allowed between tokens at [Source: (String)"\u0000\u0000\u0000\u001E{"name":"Alice","greeting":""}"; line: 1, column: 2] at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2391) at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:735) at com.fasterxml.jackson.core.base.ParserMinimalBase._throwInvalidSpace(ParserMinimalBase.java:713) at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._skipWSOrEnd(ReaderBasedJsonParser.java:2462) at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:698) at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4762) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4668) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3630) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3598) at org.example.GreetServerHandler.channelRead0(GreetServerHandler.java:26) at org.example.GreetServerHandler.channelRead0(GreetServerHandler.java:15) at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:840)
最新发布
08-26
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值