netty-daxin-2(netty常用事件讲解)

news2024/9/22 11:29:20

文章目录

  • netty常用事件讲解
    • ChannelHandler接口
      • ChannelHandler适配器类
      • ChannelInboundHandler 子接口
          • Channel 的状态
          • 调用时机
          • ChannelHandler 生命周期示例
            • NettServer&CustomizeInboundHandler
            • NettyClient
            • 测试
            • 分析
        • ChannelInboundHandlerAdapter适配器类
          • SimpleChannelInboundHandler 简单实现
      • ChannelOutboundHandler 子接口
        • ChannelOutboundHandlerAdapter适配器类
      • ChannelDuplexHandler复合类

netty常用事件讲解

ChannelHandler接口

该处理器接口用于处理io事件,和拦截io操作 ,并且调用传递给pipeline中的下一个处理器。

它下面有2个子接口,并且它们都有对应的适配器类,并且还有1个复合的ChannelDuplexHandler类,用于处理入站io事件和出站io操作

  • ChannelInboundHandler:用于处理入站io事件
  • ChannelOutboundHandler:用于处理出站io操作

上下文对象

  • 1个ChannelHandler是跟1个ChannelHandlerContext上下文对象绑定的,并且该channelHandler应该通过它所绑定的上下文对象与pipeline作交互。
  • ChannelHandler通过使用上下文对象,能够将事件传递给上游或下游的处理器,动态修改pipeline,或者使用AttributeKey去存储handler自己的数据

状态管理

  • ChannelHandler经常需要存储一些状态信息,最简单的方法就是在handler类中定义自己的成员变量。
  • 但是由于handler具有了成员变量,我们就不得不针对每个连接都创建1个新的handler实例,来避免多线程并发问题。

使用AttributeKey

  • 尽管推荐使用成员变量去存储handler自身的状态数据,但是由于某些原因,你可能并不想为每个连接都去创建1个新的handler。在这种情况下,可以使用ChannelHandlerContext提供的AttributeKey来解决这个问题(如下所示),但是目前ChannelHandlerContext#attr和ChannelHandlerContext#hasAttr都被弃用了,推荐使用Channel#attr(AttributeKey)和Channel#hasAttr。这样就可以让同一个handler实例在多个pipeline中都可以使用了。

    public class DataServerHandler extends SimpleChannelInboundHandler<Message> {
    
        private final AttributeKey<Boolean> auth = AttributeKey.valueOf("auth");
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Message msg) throws Exception {
    
            Attribute<Boolean> attr = ctx.attr(auth);
            if (msg instanceof LoginMessage) {
                authenticate((LoginMessage) msg);
                attr.set(true);
            } else if (message instanceof GetDataMessage) {
                if (Boolean.TRUE.equals(attr.get())) {
                    ctx.writeAndFlush(fetchSecret((GetDataMessage) msg));
                } else {
                    fail();
                }
            }
    
        }
    }
    

@Sharable注解

  • 如果1个ChannelHandler标注了@Sharable注解,那么可以只须创建1次该handler,就可以把它添加到不同的pipeline中,并且不会有并发安全问题。
  • 如果1个ChannelHandler没有标注@Sharable注解,就必须在每次给pipeline中添加次handler时,都需要创建1个新的handler,因为它有状态。

ChannelHandler的API

  • ChannelHandler 作为顶层接口,它并不具备太多功能,它仅仅只提供了三个 API:

    API描述
    handlerAdded()当ChannelHandler 添加到 ChannelPipeline 中时被调用
    handlerRemoved()当 ChannelHandler 被从 ChannelPipeline 移除时调用
    exceptionCaught()当 ChannelHandler 在处理过程中出现异常时调用
  • 从 ChannelHandler 提供的 API 中我们可以看出,它并不直接参与 Channel 的数据加工过程,而是用来响应 ChannelPipeline 链和异常处理的,对于 Channel 的数据加工则由它的子接口处理:

public interface ChannelHandler {

    // 当ChannelHandler 添加到 ChannelPipeline 中时被调用
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;

    // 当 ChannelHandler 被从 ChannelPipeline 移除时调用
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;

   
    // 当 ChannelHandler 在处理过程中出现异常时调用
    @Deprecated
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

    
    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sharable {
        // no value
    }
}

ChannelHandler适配器类

public abstract class ChannelHandlerAdapter implements ChannelHandler {

   
    boolean added;
 
    protected void ensureNotSharable() {
        if (isSharable()) {
            throw new IllegalStateException("ChannelHandler " + getClass().getName() + " is not allowed to be shared");
        }
    }

   
    public boolean isSharable() {
       
        Class<?> clazz = getClass();
        Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
        Boolean sharable = cache.get(clazz);
        if (sharable == null) {
            sharable = clazz.isAnnotationPresent(Sharable.class);
            cache.put(clazz, sharable);
        }
        return sharable;
    }


    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // NOOP
    }


    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // NOOP
    }

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

ChannelInboundHandler 子接口

public interface ChannelInboundHandler extends ChannelHandler {

    // Channel 被注册到EventLoop 时
    void channelRegistered(ChannelHandlerContext ctx) throws Exception;

    // Channel 从 EventLoop 中取消时
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

    // Channel 处于活跃状态,可以读写时
    void channelActive(ChannelHandlerContext ctx) throws Exception;

    // Channel 不再是活动状态且不再连接它的远程节点时
    void channelInactive(ChannelHandlerContext ctx) throws Exception;

    // Channel 读取数据时
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

    // Channel 从上一个读操作完成时
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

    // ChannelInboundHandler.fireUserEventTriggered()方法被调用时
    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;

    // Channel 的可写状态发生改变时
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;

    
    @Override
    @SuppressWarnings("deprecation")
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
Channel 的状态

出处:大明哥的死磕netty专栏 — 精密数据工匠:探索 Netty ChannelHandler 的奥秘

Channel 是有状态的,而且 Channel 也提供了判断 Channel 当前状态的 API,如下:

  • isOpen():检查 Channel 是否为 open 状态。
  • isRegistered():检查 Channel 是否为 registered 状态。
  • isActive():检查 Channel 是否为 active 状态。
    上面三个 API 对应了 Channel 四个状态:
状态描述
ChannelUnregisteredChannel 已经被创建,但还未注册到 EventLoop。此时 isOpen() 返回 true,但 isRegistered() 返回 false。
ChannelRegisteredChannel 已经被注册到 EventLoop。此时 isRegistered() 返回 true,但 isActive() 返回 false。
ChannelActiveChannel 已经处于活动状态并可以接收与发送数据。此时 isActive() 返回 true。
ChannelInactiveChannel 没有连接到远程节点

状态变更如下:
在这里插入图片描述
当 Channel 的状态发生改变时,会生成相对应的事件,这些事件会被转发给 ChannelHandler,而 ChannelHandler 中会有相对应的方法来对其进行响应。在 ChannelHandler 中定义一些与这生命周期相关的 API,如 channelRegistered() 、channelUnregistered() 、channelActive() 、channelInactive()等等,后面大明哥会详细介绍这些 API。

调用时机

调用时机如下:
在这里插入图片描述

ChannelHandler 生命周期示例
NettServer&CustomizeInboundHandler
@Slf4j
public class NettyServer11 {

    public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup(16);

        ServerBootstrap serverBootstrap = new ServerBootstrap()
                .group(boss, worker)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new CustomizeInboundHandler());
                    }
                });

        try {
            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8080)).sync();

            log.info("=======服务器启动成功=======");

            channelFuture.channel().closeFuture().sync();

        } catch (Exception e) {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

}

@Slf4j
public class CustomizeInboundHandler implements ChannelInboundHandler {

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info("【handlerAdded】- handler 添加到 ChannelPipeline");
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        log.info("【channelRegistered】- handler 注册到 eventLoop");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("【channelActive】- Channel 准备就绪");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("【channelRead】- Channel 中有可读数据");
        if (msg instanceof ByteBuf) {
            try {
                ByteBuf byteBuf = (ByteBuf) msg;
                String code = byteBuf.toString(StandardCharsets.UTF_8);
                if ("evt".equals(code)) {
                    ctx.fireUserEventTriggered("JUST A EVT~");
                } else if ("ex".equals(code)) {
                    throw new NullPointerException("NULL POINTER~");
                } else if ("write".equals(code)) {
                    ByteBuf buf = ByteBufAllocator.DEFAULT.buffer().writeBytes("Great!Well Done~".getBytes());
                    ctx.channel().writeAndFlush(buf);
                } else {
                    log.info("服务端收到客户端发送的消息: {}", code);
                }
            } finally {
                ReferenceCountUtil.release(msg);
            }

        } else {
            ctx.fireChannelRead(msg);
        }

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        log.info("【channelReadComplete】- Channel 读取数据完成");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("【channelInactive】- Channel 被关闭,不在活跃");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        log.info("【channelUnregistered】- Channel 从 EventLoop 中被取消");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        log.info("【handlerRemoved】- handler 从 ChannelPipeline 中移除");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info("【exceptionCaught】 - ChannelHandler处理发生异常");
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        log.info("【userEventTriggered】 - 激发自定义事件: {}", evt);
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        log.info("【channelWritabilityChanged】 - 可写状态改变");
    }


}
NettyClient
@Slf4j
public class NettyClient11 {

    public static void main(String[] args) throws Exception {

        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        Channel channel = new Bootstrap()
                .group(eventLoopGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                if (msg instanceof ByteBuf) {
                                    ByteBuf byteBuf = (ByteBuf) msg;
                                    log.info("客户端收到服务端发送的消息: {}", byteBuf.toString(StandardCharsets.UTF_8));
                                    ReferenceCountUtil.release(msg);
                                }
                            }
                        });
                    }
                })
                .connect("127.0.0.1", 8080)
                .sync()
                .channel();

        log.info("=======客户端连接服务器成功=======");

        Scanner sc = new Scanner(System.in);

        while (true) {

            System.out.print("输入:");

            String line = sc.nextLine();

            if (line == null || line.length() == 0) {
                continue;
            }

            if ("close".equals(line)) {
                channel.close().sync();
                eventLoopGroup.shutdownGracefully();
                break;
            }

            // 输入内容
            ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
            byteBuf.writeBytes(line.getBytes());

            channel.writeAndFlush(byteBuf);

            System.out.println("=======发送成功=======");
        }

    }

}

测试

客户端依次发送:evt、ex、write、halo、close这几个字符串,观察日志输出
服务端日志

[15:57:54] [main] com.zzhua.test11.NettyServer11 [33] - =======服务器启动成功=======
[15:58:01] [nioEventLoopGroup-3-1] 【handlerAdded】- handler 添加到 ChannelPipeline
[15:58:01] [nioEventLoopGroup-3-1] 【channelRegistered】- handler 注册到 eventLoop
[15:58:01] [nioEventLoopGroup-3-1] 【channelActive】- Channel 准备就绪

[15:58:11] [nioEventLoopGroup-3-1] 【channelRead】- Channel 中有可读数据
[15:58:11] [nioEventLoopGroup-3-1] 【channelReadComplete】- Channel 读取数据完成

[15:58:22] [nioEventLoopGroup-3-1] 【channelRead】- Channel 中有可读数据
[15:58:22] [nioEventLoopGroup-3-1] 【exceptionCaught】 - ChannelHandler处理发生异常
[15:58:22] [nioEventLoopGroup-3-1] 【channelReadComplete】- Channel 读取数据完成

[15:58:31] [nioEventLoopGroup-3-1] 【channelRead】- Channel 中有可读数据
[15:58:31] [nioEventLoopGroup-3-1] 【channelReadComplete】- Channel 读取数据完成

[15:58:46] [nioEventLoopGroup-3-1] 【channelRead】- Channel 中有可读数据
[15:58:46] [nioEventLoopGroup-3-1]  服务端收到客户端发送的消息: halo
[15:58:46] [nioEventLoopGroup-3-1] 【channelReadComplete】- Channel 读取数据完成

[15:59:01] [nioEventLoopGroup-3-1] 【channelReadComplete】- Channel 读取数据完成
[15:59:01] [nioEventLoopGroup-3-1] 【channelInactive】- Channel 被关闭,不在活跃
[15:59:01] [nioEventLoopGroup-3-1] 【channelUnregistered】- Channel 从 EventLoop 中被取消
[15:59:01] [nioEventLoopGroup-3-1] 【handlerRemoved】- handler 从 ChannelPipeline 中移除

客户端日志

[15:58:01]  [main] com.zzhua.test11.NettyClient11 [48] - =======客户端连接服务器成功=======
输入:evt
=======发送成功=======
输入:ex
=======发送成功=======
输入:write
=======发送成功=======
输入:[15:58:31] [INFO ] [nioEventLoopGroup-2-1] com.zzhua.test11.NettyClient11 [37] - 客户端收到服务端发送的消息: Great!Well Done~
halo
=======发送成功=======
输入:close
Disconnected from the target VM, address: '127.0.0.1:65133', transport: 'socket'
分析

执行过程

  • 服务端检测到客户端发起连接后,会将要处理的 Handler 添加到 ChannelPipeline 中,然后将 Channel 注册到 EventLoop,注册完成后,Channel 准备就绪处于活跃状态,可以接收消息了
  • 客户端向服务端发送消息,服务端读取消息
  • 当服务端检测到客户端已关闭连接后,该 Channel 就被关闭了,不再活跃,然后将该 Channel 从 EventLoop 取消,并将 Handler 从 ChannelPipeline 中移除。

在整个生命周期中,响应方法执行顺序如下:

  • 建立连接:handlerAdded() -> channelRegistered() -> channelActive ()
  • 数据请求:channelRead() -> channelReadComplete()
  • 关闭连接:channelReadComplete() -> channelInactive() -> channelUnregistered() -> handlerRemoved()

这里大明哥对 ChannelHandler 生命周期的方法做一个总结:

  • handlerAdded():ChannelHandler 被加入到 Pipeline 时触发。当服务端检测到新链接后,会将 ChannelHandler 构建成一个双向链表(下篇文章介绍),该方法被触发表示在当前 Channel 中已经添加了一个 ChannelHandler 业务处理链了》。
  • channelRegistered():当 Channel 注册到 EventLoop 中时被触发。该方法被触发了,表明当前 Channel 已经绑定到了某一个 EventLoop 中了。
  • channelActive():Channel 连接就绪时触发。该方法被触发,说明当前 Channel 已经处于活跃状态了,可以进行数据读写了。
  • channelRead():当 Channel 有数据可读时触发。客户端向服务端发送数据,都会触发该方法,该方法被调用说明有数据可读。而且我们自定义业务 handler 时都是重写该方法。
  • channelReadComplete():当 Channel 数据读完时触发。服务端每次读完数据后都会触发该方法,表明数据已读取完毕。
  • channelInactive():当 Channel 断开连接时触发。该方法被触发,说明 Channel 已经不再是活跃状态了,连接已经关闭了。
  • channelUnregistered():当 Channel 取消注册时触发:连接关闭后,我们就要取消该 Channel 与 EventLoop 的绑定关系了。
  • handlerRemoved():当 ChannelHandler 被从 ChannelPipeline 中移除时触发。将与该 Channel 绑定的 ChannelPipeline 中的 ChannelHandler 业务处理链全部移除。
ChannelInboundHandlerAdapter适配器类
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);
    }
}

SimpleChannelInboundHandler 简单实现
  • 它帮助我们实现了ChannelInboundHandler接口,使用时,我们只需要继承它即可
  • 它的泛型代表含义:
    • 当读到传过来的的msg是泛型所指定的类型时,它会把该msg传给channelRead0方法,交给子类去实现,并且在调用完成之后,它会帮助我们释放msg;
    • 当读到传过来的的msg不是泛型所指定的类型时,它会直接传递给下1个入站处理器,这时,它并不会帮我们释放msg。
  • 好处:它只处理指定泛型类型的消息,这样就可以避免把处理不同类型消息的代码全放在同一个类中
public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter {

    private final TypeParameterMatcher matcher;
    
    private final boolean autoRelease;


    protected SimpleChannelInboundHandler() {
        this(true);
    }


    protected SimpleChannelInboundHandler(boolean autoRelease) {
        matcher = TypeParameterMatcher.find(this, SimpleChannelInboundHandler.class, "I");
        this.autoRelease = autoRelease;
    }

 
    protected SimpleChannelInboundHandler(Class<? extends I> inboundMessageType) {
        this(inboundMessageType, true);
    }


    protected SimpleChannelInboundHandler(Class<? extends I> inboundMessageType, boolean autoRelease) {
        matcher = TypeParameterMatcher.get(inboundMessageType);
        this.autoRelease = autoRelease;
    }

    public boolean acceptInboundMessage(Object msg) throws Exception {
        return matcher.match(msg);
    }

    @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);
            }
        }
    }

    protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;
}

在使用 ChannelInboundHandlerAdapter 的时候,需要注意的是我们需要显示地释放与池化 ByteBuf 实例相关的内存,Netty 为此专门提供了一个方法 ReferenceCountUtil.release(),即我们需要在 ChannelInboundHandler 的链的末尾需要使用该方法来释放内存,如下:

public class ByteBufReleaseHandler extends ChannelInboundHandlerAdapter{
  @Override
  public void channelRead(ChannelHandlerContext ctx,Object msg){
    //释放msg
    ReferenceCountUtil.release(msg);
  }
}

但是有些小伙伴有时候会忘记这点,会带来不必要的麻烦,那有没有更好的方法呢?Netty 提供了一个类来帮助我们简化这个过程: SimpleChannelInboundHandler,对于我们业务处理的类,采用继承 SimpleChannelInboundHandler 而不是 ChannelInboundHandlerAdapter 就可以解决了。

使用 SimpleChannelInboundHandler 我们就不需要显示释放资源了,是不是非常人性化。

ChannelOutboundHandler 子接口

public interface ChannelOutboundHandler extends ChannelHandler {
    
    // 请求将 Channel 绑定到本地地址时
    void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;

    // 请求将 Channel 连接到远程节点时
    void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
                 SocketAddress localAddress, ChannelPromise promise) throws Exception;

    // 请求将 Channel 从远程节点断开时
    void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    // 请求关闭 Channel 时
    void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    // 请求将 Channel 从它的 EventLoop 注销时
    void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    // 请求从 Channel 中读取数据时
    void read(ChannelHandlerContext ctx) throws Exception;

    // 请求通过 Channel 将入队数据刷入远程节点时
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;

    // 请求通过 Channel 将数据写入远程节点时
    void flush(ChannelHandlerContext ctx) throws Exception;
}

ChannelOutboundHandlerAdapter适配器类
public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {

   
    @Skip
    @Override
    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
            ChannelPromise promise) throws Exception {
        ctx.bind(localAddress, promise);
    }

    
    @Skip
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception {
        ctx.connect(remoteAddress, localAddress, promise);
    }

    
    @Skip
    @Override
    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)
            throws Exception {
        ctx.disconnect(promise);
    }

   
    @Skip
    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise)
            throws Exception {
        ctx.close(promise);
    }

    
    @Skip
    @Override
    public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
        ctx.deregister(promise);
    }

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

   
    @Skip
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ctx.write(msg, promise);
    }

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

ChannelDuplexHandler复合类

继承自ChannelInboundHandlerAdapter,实现了ChannelOutboundHandler接口。

public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler {

    
    @Skip
    @Override
    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
                     ChannelPromise promise) throws Exception {
        ctx.bind(localAddress, promise);
    }

    
    @Skip
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
                        SocketAddress localAddress, ChannelPromise promise) throws Exception {
        ctx.connect(remoteAddress, localAddress, promise);
    }

    
    @Skip
    @Override
    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)
            throws Exception {
        ctx.disconnect(promise);
    }

   
    @Skip
    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
        ctx.close(promise);
    }

    
    @Skip
    @Override
    public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
        ctx.deregister(promise);
    }

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

   
    @Skip
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ctx.write(msg, promise);
    }

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1316255.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL学习之基础篇】多表查询

文章目录 1. 多表关系1.1. 一对多1.2. 多对多1.3. 一对一 2. 多表查询概述2.1. 数据准备2.2. 概述 3. 查询的分类3.1. 内连接查询3.2. 外连接查询3.3. 自连接3.3.1. 自连接查询3.3.2. 联合查询 3.4. 子查询3.4.1. 概述3.4.2. 标量子查询3.4.3. 列子查询3.4.4. 行子查询3.4.5. 表…

等等Domino 14.0FP1

大家好&#xff0c;才是真的好。 节奏确实太快了&#xff0c;有时候我深感我也追不上。 以前Notes Domino是三年磨一剑&#xff0c;也就说每三年才发一个大版本&#xff0c;从2019年开始&#xff0c;进行了高频提速&#xff0c;居然一年一个大版本&#xff01; 周末&#xf…

vue-json-excel 在导出一行a-table明细后再去导出另一行明细但导出结果还是和第一次一样

通过接口等拿到数据后要使用setTimeout稍微延时下再去触发导出&#xff0c;不然数还未更新就导出了

分库分表以后,如何实现扩容?

在实际开发中&#xff0c;数据库的扩容和不同的分库分表规则直接相关&#xff0c;今天我们从系统设计的角度&#xff0c;抽象了一个项目开发中出现的业务场景&#xff0c;从数据库设计、路由规则&#xff0c;以及数据迁移方案的角度进行讨论。 从业务场景出发进行讨论 假设这…

LeetCode(60)K 个一组翻转链表【链表】【困难】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; K 个一组翻转链表 1.题目 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xf…

1231. 航班时间(整行字符串输入:getline(cin,line))

题目&#xff1a; 1231. 航班时间 - AcWing题库 输入样例&#xff1a; 3 17:48:19 21:57:24 11:05:18 15:14:23 17:21:07 00:31:46 (1) 23:02:41 16:13:20 (1) 10:19:19 20:41:24 22:19:04 16:41:09 (1)输出样例&#xff1a; 04:09:05 12:10:39 14:22:05 思路&#xff1a; …

Jmeter分布式性能测试,80%资深测试都会遇到这个坑!

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

鸿蒙HarmonyOS4.0 入门与实战

一、开发准备: 熟悉鸿蒙官网安装DevEco Studio熟悉鸿蒙官网 HarmonyOS应用开发官网 - 华为HarmonyOS打造全场景新服务 应用设计相关资源: 开发相关资源: 例如开发工具 DevEco Studio 的下载 应用发布: 开发文档:

PMP项目管理 - 质量管理

系列文章目录 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the future of dream weaving wings, let the dream fly in reality. PMP项目管理 - 质量管理 系列文章目录一、规划质量管理 - 关注工作需要达到的质量二、管理…

爆火的“数字人”,你还不知道吗?

数字人是一种基于人工智能技术创建的虚拟实体&#xff0c;具有高度智能化和交互性。他们可以像真正的人类一样思考、学习和表达情感&#xff0c;与人类进行对话和互动。数字人的出现在电影中已经有了一些令人难忘的片段。 首先&#xff0c;值得一提的是电影《阿凡达》中的数字…

maui中实现加载更多 RefreshView跟ListView(1)

效果如图&#xff1a; MainPage.xaml.cs: using System; using System.Collections.ObjectModel; using System.Threading.Tasks; using Microsoft.Maui.Controls; using Microsoft.Maui.Controls.Xaml; using System.ComponentModel; using System.Runtime.CompilerServices…

基于Java SSM框架实现智能停车场系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现智能停车场系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个智能停车场管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述…

【KMP】【判断是否是重复子字符串】Leetcode 459 重复的子字符串

【KMP】【判断是否是重复子字符串】Leetcode 459 重复的子字符串 解法1 拼接字符串-掐头去尾后判断是否含有原字符串解法2 KMP——重复子串的最小单位是这个字符串里的最长相等前后缀所不包含的子串解法3 暴力解法KMP ---------------&#x1f388;&#x1f388;题目链接&…

编译 pywinhook v1.6.2 的环境设置和步骤

准备做一个鼠标事件响应程序。 查了一下相关python的第三方类库&#xff0c;发现有 pyhook。 一、起源 pyhook 1、pyhook是最早的版本 pyhook支持的python版本比较低&#xff0c;代码在 https://sourceforge.net/projects/pyhook/ 2、之后产生了两个并行版本 pyHook3 和 p…

来聊聊Spring的循环依赖

文章目录 首先了解一下什么是循环依赖简述解决循环依赖全过程通过debug了解Spring解决循环依赖全过程Aservice的创建递归来到Bservice的创建然后BService递归回到了getAservice的doGetBean中故事再次回到Aservice填充BService的步骤 总结成流程图为什么二级就能解决循环依赖问题…

六个优质开源项目,让你更了解Django框架开发

Django 是一个开源的 Web 应用框架&#xff0c;由 Python 写成。采用了 MTV 的框架模式&#xff0c;即模型 M&#xff0c;视图 V 和模版 T。它最初是被用来开发 CMS 软件的&#xff0c;所以 Django 很适合用来搭建内容类网站&#xff0c;它的设计目的是使常见的 Web 开发任务变…

【Nginx】Nginx了解(基础)

文章目录 Nginx产生的原因Nginx简介Nginx的作用反向代理负载均衡策略动静分离 Nginx的Windows下的安装Linux下的安装Nginx常用命令 负载均衡功能演示 Nginx产生的原因 背景 一个公司的项目刚刚上线的时候&#xff0c;并发量小&#xff0c;用户使用的少&#xff0c;所以在低并发…

大数据技术之Shell(超级详细)

大数据技术之Shell&#xff08;超级详细&#xff09; 第1章 Shell概述 Shell 是一种脚本语言&#xff0c;用于在操作系统的命令行界面&#xff08;CLI&#xff09;下执行命令和脚本。在大数据领域&#xff0c;Shell 脚本常用于编写数据处理和分析任务的自动化脚本&#xff0c…

B站剧场播放模式2.0

文章目录 v 1.01、新版本改进1-1 去掉了冗余1-2 剧场模式增强1-3 演示视频 2、代码 v 1.0 油猴脚本-Bilibili剧场模式仿Youtube-CSDN博客 https://blog.csdn.net/qq_45020818/article/details/131751288 功能比较粗糙&#xff0c;很多细节不完善&#xff0c;代码也写的很乱。 …

地图自定义省市区合并展示数据整合

需求一&#xff1a;将省级地图下的两个市合并成一个区域&#xff0c;中间的分割线隐藏。 1、访问下方地址&#xff0c;搜索并下载省级地图json文件。 地址&#xff1a;https://datav.aliyun.com/portal/school/atlas/area_selector 2、切换到边界生成器&#xff0c;上传刚刚下…