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

文章目录

  • 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");@Overrideprotected 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 在处理过程中出现异常时调用@Deprecatedvoid 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;}@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {// NOOP}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {// NOOP}@Skip@Override@Deprecatedpublic 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>() {@Overrideprotected 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 {@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {log.info("【handlerAdded】- handler 添加到 ChannelPipeline");}@Overridepublic void channelRegistered(ChannelHandlerContext ctx) throws Exception {log.info("【channelRegistered】- handler 注册到 eventLoop");}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.info("【channelActive】- Channel 准备就绪");}@Overridepublic 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);}}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {log.info("【channelReadComplete】- Channel 读取数据完成");}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {log.info("【channelInactive】- Channel 被关闭,不在活跃");}@Overridepublic void channelUnregistered(ChannelHandlerContext ctx) throws Exception {log.info("【channelUnregistered】- Channel 从 EventLoop 中被取消");}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {log.info("【handlerRemoved】- handler 从 ChannelPipeline 中移除");}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {log.info("【exceptionCaught】 - ChannelHandler处理发生异常");}@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {log.info("【userEventTriggered】 - 激发自定义事件: {}", evt);}@Overridepublic 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>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic 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@Overridepublic void channelRegistered(ChannelHandlerContext ctx) throws Exception {ctx.fireChannelRegistered();}@Skip@Overridepublic void channelUnregistered(ChannelHandlerContext ctx) throws Exception {ctx.fireChannelUnregistered();}@Skip@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {ctx.fireChannelActive();}@Skip@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {ctx.fireChannelInactive();}@Skip@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ctx.fireChannelRead(msg);}@Skip@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.fireChannelReadComplete();}@Skip@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {ctx.fireUserEventTriggered(evt);}@Skip@Overridepublic 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);}@Overridepublic 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{@Overridepublic void channelRead(ChannelHandlerContext ctx,Object msg){//释放msgReferenceCountUtil.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@Overridepublic void bind(ChannelHandlerContext ctx, SocketAddress localAddress,ChannelPromise promise) throws Exception {ctx.bind(localAddress, promise);}@Skip@Overridepublic void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,SocketAddress localAddress, ChannelPromise promise) throws Exception {ctx.connect(remoteAddress, localAddress, promise);}@Skip@Overridepublic void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)throws Exception {ctx.disconnect(promise);}@Skip@Overridepublic void close(ChannelHandlerContext ctx, ChannelPromise promise)throws Exception {ctx.close(promise);}@Skip@Overridepublic void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {ctx.deregister(promise);}@Skip@Overridepublic void read(ChannelHandlerContext ctx) throws Exception {ctx.read();}@Skip@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {ctx.write(msg, promise);}@Skip@Overridepublic void flush(ChannelHandlerContext ctx) throws Exception {ctx.flush();}
}

ChannelDuplexHandler复合类

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

public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler {@Skip@Overridepublic void bind(ChannelHandlerContext ctx, SocketAddress localAddress,ChannelPromise promise) throws Exception {ctx.bind(localAddress, promise);}@Skip@Overridepublic void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,SocketAddress localAddress, ChannelPromise promise) throws Exception {ctx.connect(remoteAddress, localAddress, promise);}@Skip@Overridepublic void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)throws Exception {ctx.disconnect(promise);}@Skip@Overridepublic void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {ctx.close(promise);}@Skip@Overridepublic void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {ctx.deregister(promise);}@Skip@Overridepublic void read(ChannelHandlerContext ctx) throws Exception {ctx.read();}@Skip@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {ctx.write(msg, promise);}@Skip@Overridepublic void flush(ChannelHandlerContext ctx) throws Exception {ctx.flush();}
}

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

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

相关文章

LeetCode-反转链表问题

1.反转链表 题目描述&#xff1a; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 思路&#xff1a; 反转链表也就是链表倒置&#xff0c;我一直以来的办法就是先建立一个头节点&#xff0c;之后再遍历链表来进行头插。 代码&#xff1…

比尔盖茨最新文章——AI 将彻底改变计算机的使用形式

最近发现比尔盖茨还在写文章&#xff0c;确实了不起&#xff0c;68 岁的老人家还在坚持输出&#xff0c;除了写文章&#xff0c;比尔盖茨还致力于教育、医疗和卫生等慈善工作&#xff0c;奋斗在一线&#xff0c;看来美国人也延迟退休啊 &#x1f605; 原文《AI 将彻底改变计算…

Java数据结构篇——单链表的基本操作

1. 前言 在上一篇《Java数据结构篇——实现顺序表的增删查改》&#xff0c;我们已经熟悉了 ArrayList 的使用并且进行了简单的模拟实现。ArrayList底层使用数组来存储元素&#xff0c;由于其底层是一段连续的空间&#xff0c;当ArrayList 任意位置插入或者删除元素时&#xff…

《使用ThinkPHP6开发项目》 - 登录接口一

《使用ThinkPHP6开发项目》 - 安装ThinkPHP框架-CSDN博客 《使用ThinkPHP6开发项目》 - 设置项目环境变量-CSDN博客 《使用ThinkPHP6开发项目》 - 项目使用多应用开发-CSDN博客 《使用ThinkPHP6开发项目》 - 创建应用-CSDN博客 《使用ThinkPHP6开发项目》 - 创建控制器-CSD…

SystemVerilog基础:并行块fork-join、join_any、join_none(二)

相关阅读 SystemVerilog基础https://blog.csdn.net/weixin_45791458/category_12517449.html 在第一节中&#xff0c;我们讨论了并行块中的fork-join块和fork-join_any块&#xff0c;了解了它们的差异&#xff0c;本文将继续讨论fork-join_none块的使用。 fork-join_none并行块…

12.12_黑马数据结构与算法笔记Java

目录 079 优先级队列 无序数组实现 080 优先级队列 有序数组实现 081 优先级队列 堆实现 1 082 优先级队列 堆实现 2 083 优先级队列 堆实现 3 084 优先级队列 e01 合并多个有序链表1 084 优先级队列 e01 合并多个有序链表2 085 阻塞队列 问题提出 086 阻塞队列 单锁实…

5个创建在线帮助文档的好方法!

在线帮助文档是企业为用户提供支持服务的重要工具&#xff0c;它能够帮助用户更好地了解和使用产品&#xff0c;提高用户体验。然而&#xff0c;创建一份优秀的在线帮助文档需要掌握一定的技巧和方法。接下来就介绍一下创建在线帮助文档的5个好方法&#xff0c;帮助企业更好地为…

【JavaWeb学习笔记】10 - 手写Tomcat底层,Maven的初步使用

一、Maven 1.Maven示意图 类似Java访问数据库 2.创建Maven案例演示 配置阿里镜像 找到setting目录 但一开始配置不存在该文件 需要去Maven主目录下的conf拿到settings拷贝到上述目录 拷贝到admin/.m2后打开该settings 在<mirrors>内输入镜像地址 <mirror> …

Docker--Docker镜像仓库

一、搭建私有镜像仓库 搭建镜像仓库可以基于Docker官方提供的DockerRegistry来实现。 官网地址&#xff1a;https://hub.docker.com/_/registry &#xff08;一&#xff09;简化版镜像仓库 Docker官方的Docker Registry是一个基础版本的Docker镜像仓库&#xff0c;具备仓库…

线下实体门店引流,百万私域电商高手都在用的实战营销引流技巧!

线下实体门店引流&#xff0c;百万私域电商高手都在用的实战营销引流技巧&#xff01; 无论是初创公司还是已经在步入正轨的实体门店&#xff0c;有个现实的实例告诉你&#xff1a;互联网上90%的引流技巧告诉你的方法&#xff0c;其实都是不挣钱的&#xff0c;辛辛苦苦折腾一整…

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

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

Redis设计与实现之对象处理机制

目录 一、前言 二、对象处理机制 1、redisObject 数据结构&#xff0c;以及 Redis 的数据类型 2、 命令的类型检查和多态 3、对象共享 4、引用计数以及对象的销毁 三、对象的处理 1、Redis是如何处理字符串对象的&#xff1f; 2、Redis是如何处理列表对象的&#xff1f…

十九)Stable Diffusion使用教程:ai室内设计案例

今天我们聊聊如何通过SD进行室内设计装修。 方式一:controlnet的seg模型 基础起手式: 选择常用算法,抽卡: 抽到喜欢的图片之后,拖到controlnet里: 选择seg的ade20k预处理器,点击爆炸按钮,得到seg语义分割图,下载下来: 根据语义分割表里的颜色值,到PS里进行修改: 语…

制作一个简单 的maven plugin

流程 首先&#xff0c; 你需要创建一个Maven项目&#xff0c;推荐用idea 创建项目 会自动配置插件 pom.xml文件中添加以下配置&#xff1a; <project> <!-- 项目的基本信息 --> <groupId>com.example</groupId> <artifactId>my-maven-plugi…

深入理解JVM设计的精髓与独特之处

这是Java代码的执行过程 从软件工程的视角去深入拆解&#xff0c;无疑极具吸引力&#xff1a;首个阶段仅依赖于源高级语言的细微之处&#xff0c;而第二阶段则仅仅专注于目标机器语言的特质。 不可否认&#xff0c;在这两个编译阶段之间的衔接&#xff08;具体指明中间处理步…

javacv的视频截图功能

之前做了一个资源库的小项目&#xff0c;因为上传资源文件包含视频等附件&#xff0c;所以就需要时用到这个功能。通过对视频截图&#xff0c;然后作为封面缩略图&#xff0c;达到美观效果。 首先呢&#xff0c;需要准备相关的jar包&#xff0c;之前我用的是低版本的1.4.2&…

极简Excel公式拆分合并单元格并自动填充

例如这个表格&#xff1a; 我们希望拆分合并单元格&#xff0c;并填充到E列。结果如&#xff1a; 步骤 1&#xff09;在E2输入公式如下&#xff1a; LOOKUP(2,1/($B$2:B2<>""),$B$2:B2) 2&#xff09;下拉E2至E9将公式填充即可 注意&#xff1a;公式中的$…

基于ssm游戏美术外包管理信息系统源码和论文

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;线下管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&…

[Linux] Apache的配置与运用

一、web虚拟主机的构台服务器上运行多个网站&#xff0c;每个网站实际上并不独立占用整个服务器&#xff0c;因此称为"虚拟"虚拟主机的虚拟主机服务可以让您充分利用服务器的硬件资源&#xff0c;大大降低了建立和运营网站的成本 Httpd服务使构建虚拟主机服务器变得容…

基于SSM的志愿者管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…