一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(六)

发布于 2022-7-18 17:24
浏览
0收藏

5. ChanneHandlerContext 的创建


在介绍完 ChannelHandler 向 pipeline 添加的整个逻辑过程后,本小节我们来看下如何为 ChannelHandler 创建对应的 ChannelHandlerContext ,以及 ChannelHandlerContext 中具体包含了哪些上下文信息。

public class DefaultChannelPipeline implements ChannelPipeline {
    @Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
             
            ................

            //创建channelHandlerContext包裹channelHandler并封装执行传播相关的上下文信息
            newCtx = newContext(group, filterName(name, handler), handler);

             ................
        }

    }

    private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
        return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
    }

}

在创建 ChannelHandlerContext 之前,需要做两个重要的前置操作:

 

 •通过 filterName 方法为 ChannelHandlerContext 过滤出在 pipeline 中唯一的名称。

 

 •如果用户为 ChannelHandler 指定了特殊的 EventExecutorGroup ,这里就需要通过 childExecutor 方法从指定的 EventExecutorGroup 中选出一个 EventExecutor 与 ChannelHandler 绑定。


5.1 filterName

  private String filterName(String name, ChannelHandler handler) {
        if (name == null) {
            // 如果没有指定name,则会为handler默认生成一个name,该方法可确保默认生成的name在pipeline中不会重复
            return generateName(handler);
        }

        // 如果指定了name,需要确保name在pipeline中是唯一的
        checkDuplicateName(name);
        return name;
    }

如果用户再向 pipeline 添加 ChannelHandler 的时候,为其指定了具体的名称,那么这里需要确保用户指定的名称在 pipeline 中是唯一的。

 

private void checkDuplicateName(String name) {
        if (context0(name) != null) {
            throw new IllegalArgumentException("Duplicate handler name: " + name);
        }
    }

    /**
     * 通过指定名称在pipeline中查找对应的channelHandler 没有返回null
     * */
    private AbstractChannelHandlerContext context0(String name) {
        AbstractChannelHandlerContext context = head.next;
        while (context != tail) {
            if (context.name().equals(name)) {
                return context;
            }
            context = context.next;
        }
        return null;
    }

如果用户没有为 ChannelHandler 指定名称,那么就需要为 ChannelHandler 在 pipeline 中默认生成一个唯一的名称。

 

 // pipeline中channelHandler对应的name缓存
    private static final FastThreadLocal<Map<Class<?>, String>> nameCaches =
            new FastThreadLocal<Map<Class<?>, String>>() {
        @Override
        protected Map<Class<?>, String> initialValue() {
            return new WeakHashMap<Class<?>, String>();
        }
    };

    private String generateName(ChannelHandler handler) {
        // 获取pipeline中channelHandler对应的name缓存
        Map<Class<?>, String> cache = nameCaches.get();
        Class<?> handlerType = handler.getClass();
        String name = cache.get(handlerType);
        if (name == null) {
            // 当前handler还没对应的name缓存,则默认生成:simpleClassName + #0
            name = generateName0(handlerType);
            cache.put(handlerType, name);
        }

        if (context0(name) != null) {
            // 不断重试名称后缀#n + 1 直到没有重复
            String baseName = name.substring(0, name.length() - 1); 
            for (int i = 1;; i ++) {
                String newName = baseName + i;
                if (context0(newName) == null) {
                    name = newName;
                    break;
                }
            }
        }
        return name;
    }

    private static String generateName0(Class<?> handlerType) {
        return StringUtil.simpleClassName(handlerType) + "#0";
    }

pipeline 中使用了一个 FastThreadLocal 类型的 nameCaches 来缓存各种类型 ChannelHandler 的基础名称。后面会根据这个基础名称不断的重试生成一个没有冲突的正式名称。缓存 nameCaches 中的 key 表示特定的 ChannelHandler 类型,value 表示该特定类型的 ChannelHandler 的基础名称  simpleClassName + #0

 

自动为 ChannelHandler 生成默认名称的逻辑是:

 

 •首先从缓存中 nameCaches 获取当前添加的 ChannelHandler 的基础名称 simpleClassName + #0


 •如果该基础名称 simpleClassName + #0 在 pipeline 中是唯一的,那么就将基础名称作为 ChannelHandler 的名称。

 

 •如果缓存的基础名称在 pipeline 中不是唯一的,则不断的增加名称后缀 simpleClassName#1 ,simpleClassName#2 ...... simpleClassName#n 直到产生一个没有重复的名称。


虽然用户不大可能将同一类型的 channelHandler 重复添加到 pipeline 中,但是 netty 为了防止这种反复添加同一类型 ChannelHandler 的行为导致的名称冲突,从而利用 nameCaches 来缓存同一类型 ChannelHandler 的基础名称 simpleClassName + #0,然后通过不断的重试递增名称后缀,来生成一个在pipeline中唯一的名称。

 

5.2 childExecutor


通过前边的介绍我们了解到,当我们向 pipeline 添加 ChannelHandler 的时候,netty 允许我们为 ChannelHandler 指定特定的 executor 去执行 ChannelHandler 中的各种事件回调方法。

 

通常我们会为 ChannelHandler 指定一个EventExecutorGroup,在创建ChannelHandlerContext 的时候,会通过 childExecutor 方法从 EventExecutorGroup 中选取一个 EventExecutor 来与该 ChannelHandler 绑定。

 

EventExecutorGroup 是 netty 自定义的一个线程池模型,其中包含多个 EventExecutor ,而 EventExecutor 在 netty 中是一个线程的执行模型。相关的具体实现和用法笔者已经在《Reactor在Netty中的实现(创建篇)》一文中给出了详尽的介绍,忘记的同学可以在回顾下。


在介绍 executor 的绑定逻辑之前,这里笔者需要先为大家介绍一个相关的重要参数:SINGLE_EVENTEXECUTOR_PER_GROUP ,默认为 true 。

ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
  .........
.childOption(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP,true)

我们知道在 netty 中,每一个 channel 都会对应一个独立的 pipeline ,如果我们开启了 SINGLE_EVENTEXECUTOR_PER_GROUP 参数,表示在一个 channel 对应的 pipeline 中,如果我们为多个 ChannelHandler 指定了同一个 EventExecutorGroup ,那么这多个 channelHandler 只能绑定到 EventExecutorGroup 中的同一个 EventExecutor 上。

 

什么意思呢??比如我们有下面一段初始化pipeline的代码:

         

ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                 ........................
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline pipeline = ch.pipeline();
                     pipeline.addLast(eventExecutorGroup,channelHandler1)
                     pipeline.addLast(eventExecutorGroup,channelHandler2)
                     pipeline.addLast(eventExecutorGroup,channelHandler3)
                 }
             });

eventExecutorGroup 中包含 EventExecutor1,EventExecutor2 , EventExecutor3 三个执行线程。

 

假设此时第一个连接进来,在创建 channel1 后初始化 pipeline1 的时候,如果在开启 SINGLE_EVENTEXECUTOR_PER_GROUP 参数的情况下,那么在 channel1 对应的 pipeline1 中 channelHandler1,channelHandler2 , channelHandler3 绑定的 EventExecutor 均为 EventExecutorGroup 中的 EventExecutor1 。

 

第二个连接 channel2 对应的 pipeline2 中 channelHandler1 , channelHandler2 ,channelHandler3 绑定的 EventExecutor 均为 EventExecutorGroup 中的 EventExecutor2 。

 

第三个连接 channel3 对应的 pipeline3 中 channelHandler1 , channelHandler2 ,channelHandler3 绑定的 EventExecutor 均为 EventExecutorGroup 中的 EventExecutor3 。

 

以此类推........

一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(六)-开源基础软件社区

 SINGLE_EVENTEXECUTOR_PER_GROUP绑定.png


如果在关闭 SINGLE_EVENTEXECUTOR_PER_GROUP 参数的情况下, channel1 对应的 pipeline1 中 channelHandler1 会绑定到 EventExecutorGroup 中的 EventExecutor1 ,channelHandler2 会绑定到 EventExecutor2 ,channelHandler3 会绑定到 EventExecutor3 。

 

同理其他 channel 对应的 pipeline 中的 channelHandler 绑定逻辑同 channel1 。它们均会绑定到 EventExecutorGroup 中的不同 EventExecutor 中。

一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(六)-开源基础软件社区

 SINGLE_EVENTEXECUTOR_PER_GROUP关闭.png


当我们了解了 SINGLE_EVENTEXECUTOR_PER_GROUP 参数的作用之后,再来看下面这段绑定逻辑就很容易理解了。

 

 // 在每个pipeline中都会保存EventExecutorGroup中绑定的线程
    private Map<EventExecutorGroup, EventExecutor> childExecutors;

    private EventExecutor childExecutor(EventExecutorGroup group) {
        if (group == null) {
            return null;
        }

        Boolean pinEventExecutor = channel.config().getOption(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP);
        if (pinEventExecutor != null && !pinEventExecutor) {
            //如果没有开启SINGLE_EVENTEXECUTOR_PER_GROUP,则按顺序从指定的EventExecutorGroup中为channelHandler分配EventExecutor
            return group.next();
        }

        //获取pipeline绑定到EventExecutorGroup的线程(在一个pipeline中会为每个指定的EventExecutorGroup绑定一个固定的线程)
        Map<EventExecutorGroup, EventExecutor> childExecutors = this.childExecutors;
        if (childExecutors == null) {
            childExecutors = this.childExecutors = new IdentityHashMap<EventExecutorGroup, EventExecutor>(4);
        }

        //获取该pipeline绑定在指定EventExecutorGroup中的线程
        EventExecutor childExecutor = childExecutors.get(group);
        if (childExecutor == null) {
            childExecutor = group.next();
            childExecutors.put(group, childExecutor);
        }
        return childExecutor;
    }

如果我们并未特殊指定 ChannelHandler 的 executor ,那么默认会是对应 channel 绑定的 reactor 线程负责执行该 ChannelHandler 。

 

如果我们未开启 SINGLE_EVENTEXECUTOR_PER_GROUP ,netty 就会从我们指定的 EventExecutorGroup 中按照 round-robin 的方式为 ChannelHandler 绑定其中一个 eventExecutor 。

一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(六)-开源基础软件社区

 SINGLE_EVENTEXECUTOR_PER_GROUP关闭.png


如果我们开启了 SINGLE_EVENTEXECUTOR_PER_GROUP ,相同的 EventExecutorGroup 在同一个 pipeline 实例中的绑定关系是固定的。在 pipeline 中如果多个 channelHandler 指定了同一个 EventExecutorGroup ,那么这些 channelHandler 的 executor 均会绑定到一个固定的 eventExecutor 上。一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(六)-开源基础软件社区

SINGLE_EVENTEXECUTOR_PER_GROUP绑定.png


这种固定的绑定关系缓存于每个 pipeline 中的 Map<EventExecutorGroup, EventExecutor> childExecutors 字段中,key 是用户为 channelHandler 指定的 EventExecutorGroup ,value 为该 EventExecutorGroup 在 pipeline 实例中的绑定 eventExecutor 。

 

接下来就是从 childExecutors 中获取指定 EventExecutorGroup 在该 pipeline 实例中的绑定 eventExecutor,如果绑定关系还未建立,则通过 round-robin 的方式从 EventExecutorGroup 中选取一个 eventExecutor 进行绑定,并在 childExecutor 中缓存绑定关系。

 

如果绑定关系已经建立,则直接为 ChannelHandler 指定绑定好的 eventExecutor。

 

5.3 ChanneHandlerContext


在介绍完创建 ChannelHandlerContext 的两个前置操作后,我们回头来看下 ChannelHandlerContext 中包含了哪些具体的上下文信息。

final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {

    // ChannelHandlerContext包裹的channelHandler
    private final ChannelHandler handler;

    DefaultChannelHandlerContext(
            DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
        super(pipeline, executor, name, handler.getClass());
        //包裹的channelHandler
        this.handler = handler;
    }

    @Override
    public ChannelHandler handler() {
        return handler;
    }
}
abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
    //对应channelHandler的名称
    private final String name;

    //ChannelHandlerContext中持有pipeline的引用
    private final DefaultChannelPipeline pipeline;

    // channelHandler对应的executor 默认为reactor
    final EventExecutor executor;

    //channelHandlerContext中保存channelHandler的执行条件掩码(是什么类型的ChannelHandler,对什么事件感兴趣)
    private final int executionMask;

    //false表示 当channelHandler的状态为ADD_PENDING的时候,也可以响应pipeline中的事件
    //true表示只有在channelHandler的状态为ADD_COMPLETE的时候才能响应pipeline中的事件
    private final boolean ordered;

    //channelHandelr的状态,初始化为INIT
    private volatile int handlerState = INIT;

    AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,
                                  String name, Class<? extends ChannelHandler> handlerClass) {
        this.name = ObjectUtil.checkNotNull(name, "name");
        this.pipeline = pipeline;
        this.executor = executor;
        //channelHandlerContext中保存channelHandler的执行条件掩码(是什么类型的ChannelHandler,对什么事件感兴趣)
        this.executionMask = mask(handlerClass);
        ordered = executor == null || executor instanceof OrderedEventExecutor;
    }

}

 ChannelHandlerContext.png


这里笔者重点介绍 orderd 属性和 executionMask 属性,其他的属性大家很容易理解。

  ordered = executor == null || executor instanceof OrderedEventExecutor;

当我们不指定 channelHandler 的 executor 时或者指定的 executor 类型为 OrderedEventExecutor 时,ordered = true。

 

那么这个 ordered 属性对于 ChannelHandler 响应 pipeline 中的事件有什么影响呢?

 

我们之前介绍过在 ChannelHandler 响应 pipeline 中的事件之前都会调用 invokeHandler() 方法来判断是否回调 ChannelHandler 的事件回调方法还是跳过。

   private boolean invokeHandler() {
        int handlerState = this.handlerState;
        return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
    }

 •当 ordered == false 时,channelHandler 的状态为 ADD_PENDING 的时候,也可以响应 pipeline 中的事件。


 •当 ordered == true 时,只有在 channelHandler 的状态为 ADD_COMPLETE 的时候才能响应 pipeline 中的事件


另一个重要的属性 executionMask 保存的是当前 ChannelHandler 的一些执行条件信息掩码,比如:

 

 •当前 ChannelHandler 是什么类型的( ChannelInboundHandler or ChannelOutboundHandler ?)。


 •当前 ChannelHandler 对哪些事件感兴趣(覆盖了哪些事件回调方法?)
 

private static final FastThreadLocal<Map<Class<? extends ChannelHandler>, Integer>> MASKS =
            new FastThreadLocal<Map<Class<? extends ChannelHandler>, Integer>>() {
                @Override
                protected Map<Class<? extends ChannelHandler>, Integer> initialValue() {
                    return new WeakHashMap<Class<? extends ChannelHandler>, Integer>(32);
                }
            };

    static int mask(Class<? extends ChannelHandler> clazz) {
        // 因为每建立一个channel就会初始化一个pipeline,这里需要将ChannelHandler对应的mask缓存
        Map<Class<? extends ChannelHandler>, Integer> cache = MASKS.get();
        Integer mask = cache.get(clazz);
        if (mask == null) {
            // 计算ChannelHandler对应的mask(什么类型的ChannelHandler,对什么事件感兴趣)
            mask = mask0(clazz);
            cache.put(clazz, mask);
        }
        return mask;
    }

这里需要一个 FastThreadLocal 类型的 MASKS 字段来缓存 ChannelHandler 对应的执行掩码。因为 ChannelHandler 类一旦被定义出来它的执行掩码就固定了,而 netty 需要接收大量的连接,创建大量的 channel ,并为这些 channel 初始化对应的 pipeline ,需要频繁的记录 channelHandler 的执行掩码到 context 类中,所以这里需要将掩码缓存起来。

 

 private static int mask0(Class<? extends ChannelHandler> handlerType) {
        int mask = MASK_EXCEPTION_CAUGHT;
        try {
            if (ChannelInboundHandler.class.isAssignableFrom(handlerType)) {
                //如果该ChannelHandler是Inbound类型的,则先将inbound事件全部设置进掩码中
                mask |= MASK_ALL_INBOUND;

                //最后在对不感兴趣的事件一一排除(handler中的事件回调方法如果标注了@Skip注解,则认为handler对该事件不感兴趣)
                if (isSkippable(handlerType, "channelRegistered", ChannelHandlerContext.class)) {
                    mask &= ~MASK_CHANNEL_REGISTERED;
                }
                if (isSkippable(handlerType, "channelUnregistered", ChannelHandlerContext.class)) {
                    mask &= ~MASK_CHANNEL_UNREGISTERED;
                }
                if (isSkippable(handlerType, "channelActive", ChannelHandlerContext.class)) {
                    mask &= ~MASK_CHANNEL_ACTIVE;
                }
                if (isSkippable(handlerType, "channelInactive", ChannelHandlerContext.class)) {
                    mask &= ~MASK_CHANNEL_INACTIVE;
                }
                if (isSkippable(handlerType, "channelRead", ChannelHandlerContext.class, Object.class)) {
                    mask &= ~MASK_CHANNEL_READ;
                }
                if (isSkippable(handlerType, "channelReadComplete", ChannelHandlerContext.class)) {
                    mask &= ~MASK_CHANNEL_READ_COMPLETE;
                }
                if (isSkippable(handlerType, "channelWritabilityChanged", ChannelHandlerContext.class)) {
                    mask &= ~MASK_CHANNEL_WRITABILITY_CHANGED;
                }
                if (isSkippable(handlerType, "userEventTriggered", ChannelHandlerContext.class, Object.class)) {
                    mask &= ~MASK_USER_EVENT_TRIGGERED;
                }
            }

            if (ChannelOutboundHandler.class.isAssignableFrom(handlerType)) {
                //如果handler为Outbound类型的,则先将全部outbound事件设置进掩码中
                mask |= MASK_ALL_OUTBOUND;

                //最后对handler不感兴趣的事件从掩码中一一排除
                if (isSkippable(handlerType, "bind", ChannelHandlerContext.class,
                        SocketAddress.class, ChannelPromise.class)) {
                    mask &= ~MASK_BIND;
                }
                if (isSkippable(handlerType, "connect", ChannelHandlerContext.class, SocketAddress.class,
                        SocketAddress.class, ChannelPromise.class)) {
                    mask &= ~MASK_CONNECT;
                }
                if (isSkippable(handlerType, "disconnect", ChannelHandlerContext.class, ChannelPromise.class)) {
                    mask &= ~MASK_DISCONNECT;
                }
                if (isSkippable(handlerType, "close", ChannelHandlerContext.class, ChannelPromise.class)) {
                    mask &= ~MASK_CLOSE;
                }
                if (isSkippable(handlerType, "deregister", ChannelHandlerContext.class, ChannelPromise.class)) {
                    mask &= ~MASK_DEREGISTER;
                }
                if (isSkippable(handlerType, "read", ChannelHandlerContext.class)) {
                    mask &= ~MASK_READ;
                }
                if (isSkippable(handlerType, "write", ChannelHandlerContext.class,
                        Object.class, ChannelPromise.class)) {
                    mask &= ~MASK_WRITE;
                }
                if (isSkippable(handlerType, "flush", ChannelHandlerContext.class)) {
                    mask &= ~MASK_FLUSH;
                }
            }

            if (isSkippable(handlerType, "exceptionCaught", ChannelHandlerContext.class, Throwable.class)) {
                mask &= ~MASK_EXCEPTION_CAUGHT;
            }
        } catch (Exception e) {
            // Should never reach here.
            PlatformDependent.throwException(e);
        }

        //计算出的掩码需要缓存,因为每次向pipeline中添加该类型的handler的时候都需要获取掩码(创建一个channel 就需要为其初始化pipeline)
        return mask;
    }

计算 ChannelHandler 的执行掩码 mask0 方法虽然比较长,但是逻辑却十分简单。在本文的第三小节《3. pipeline中的事件分类》中,笔者为大家详细介绍了各种事件类型的掩码表示,这里我来看下如何利用这些基本事件掩码来计算出 ChannelHandler 的执行掩码的。

 

如果 ChannelHandler 是 ChannelInboundHandler 类型的,那么首先会将所有 Inbound 事件掩码设置进执行掩码 mask 中。

 

最后挨个遍历所有 Inbound 事件,从掩码集合 mask 中排除该 ChannelHandler 不感兴趣的事件。这样一轮下来,就得到了 ChannelHandler 的执行掩码。

 

从这个过程中我们可以看到,ChannelHandler 的执行掩码包含的是该 ChannelHandler 感兴趣的事件掩码集合。当事件在 pipeline 中传播的时候,在 ChannelHandlerContext 中可以利用这个执行掩码来判断,当前 ChannelHandler 是否符合响应该事件的资格。

 

同理我们也可以计算出 ChannelOutboundHandler 类型的 ChannelHandler 对应的执行掩码。

 

那么 netty 框架是如何判断出我们自定义的 ChannelHandler 对哪些事件感兴趣,对哪些事件不感兴趣的呢?

 

这里我们以 ChannelInboundHandler 类型举例说明,在本文第三小节中,笔者对所有 Inbound 类型的事件作了一个全面的介绍,但是在实际开发中,我们可能并不需要监听所有的 Inbound 事件,可能只是需要监听其中的一到两个事件。

 

对于我们不感兴趣的事件,我们只需要在其对应的回调方法上标注 @Skip 注解即可,netty 就会认为该 ChannelHandler 对标注 @Skip 注解的事件不感兴趣,当不感兴趣的事件在 pipeline 传播的时候,该 ChannelHandler 就不需要执行响应。

 

private static boolean isSkippable(
            final Class<?> handlerType, final String methodName, final Class<?>... paramTypes) throws Exception {
        return AccessController.doPrivileged(new PrivilegedExceptionAction<Boolean>() {
            @Override
            public Boolean run() throws Exception {
                Method m;
                try {
                    // 首先查看类中是否覆盖实现了对应的事件回调方法
                    m = handlerType.getMethod(methodName, paramTypes);
                } catch (NoSuchMethodException e) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                            "Class {} missing method {}, assume we can not skip execution", handlerType, methodName, e);
                    }
                    return false;
                }
                return m != null && m.isAnnotationPresent(Skip.class);
            }
        });
    }

那我们在编写自定义 ChannelHandler 的时候是不是要在 ChannelInboundHandler 或者 ChannelOutboundHandler 接口提供的所有事件回调方法上,对我们不感兴趣的事件繁琐地一一标注 @Skip 注解呢?

 

其实是不需要的,netty 为我们提供了 ChannelInboundHandlerAdapter 类和 ChannelOutboundHandlerAdapter 类,netty 事先已经在这些 Adapter 类中的事件回调方法上全部标注了 @Skip 注解,我们在自定义实现 ChannelHandler 的时候只需要继承这些 Adapter 类并覆盖我们感兴趣的事件回调方法即可。

public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {

    @Skip
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelRegistered();
    }

    @Skip
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelUnregistered();
    }

    @Skip
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelActive();
    }

    @Skip
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelInactive();
    }

    @Skip
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ctx.fireChannelRead(msg);
    }

    @Skip
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelReadComplete();
    }

    @Skip
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        ctx.fireUserEventTriggered(evt);
    }

    @Skip
    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelWritabilityChanged();
    }

    @Skip
    @Override
    @SuppressWarnings("deprecation")
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        ctx.fireExceptionCaught(cause);
    }
}

标签
已于2022-7-18 17:24:01修改
收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐