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 四个状态:
状态 | 描述 |
---|---|
ChannelUnregistered | Channel 已经被创建,但还未注册到 EventLoop。此时 isOpen() 返回 true,但 isRegistered() 返回 false。 |
ChannelRegistered | Channel 已经被注册到 EventLoop。此时 isRegistered() 返回 true,但 isActive() 返回 false。 |
ChannelActive | Channel 已经处于活动状态并可以接收与发送数据。此时 isActive() 返回 true。 |
ChannelInactive | Channel 没有连接到远程节点 |
状态变更如下:
当 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();}
}
相关文章:
netty-daxin-2(netty常用事件讲解)
文章目录 netty常用事件讲解ChannelHandler接口ChannelHandler适配器类ChannelInboundHandler 子接口Channel 的状态调用时机ChannelHandler 生命周期示例NettServer&CustomizeInboundHandlerNettyClient测试分析 ChannelInboundHandlerAdapter适配器类SimpleChannelInboun…...
使用playbook部署k8s集群
1.部署ansible集群 使用python脚本一个简单的搭建ansible集群-CSDN博客 2.ansible命令搭建k8s: 1.主机规划: 节点IP地址操作系统配置server192.168.174.150centos7.92G2核client1192.168.174.151centos7.92G2核client2192.168.174.152centos7.92G2 …...
Python基础入门第四节,第五节课笔记
第四节 第一个条件语句 if 条件: 条件成立执行的代码1 条件成立执行的代码2 ...... else: 条件不成立执行的代码1 条件不成立执行的代码2 …… 代码如下: 身高 float(input("请输入您的身高(米):")) if 身高 >1.3:print(f您的身高是{身高},已经超过1.3米,您需…...
基于Java SSM框架实现智能停车场系统项目【项目源码+论文说明】
基于java的SSM框架实现智能停车场系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个智能停车场管理系统,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,面向对象编程思想进行项目开发。在引言中,作者将论述…...
React系列:useEffect的使用
useEffect的使用 useEffect的第二个参数不同,useEffect的加载不同 当第二个参数为没有的时候 只在组件初始渲染和组件更新之后加载当第二个参数为[] 的时候 只在初始渲染之后加载当第二个参数为[有依赖] 的时候 只在初始渲染之后和依赖修改的时候进行加载 functi…...
Ps:形状工具 - 描边选项
在形状工具的工具选项栏或“属性”面板中,单击“设置形状描边类型” Set shape stroke type菜单图标可打开“描边选项” Stroke Options面板。 描边预设 Stroke Type 默认列出了实线、虚线和点线三种类型的描边,单击可应用。 自己创建并存储的描边类型&a…...
C#基础知识 - 变量、常量与数据类型篇
C#基础知识 - 变量、常量与数据类型篇 第3节 变量、常量与数据类型3.1 C#变量3.1.1 变量使用3.1.2 自定义变量3.1.2 接收用户输入 3.2 C#常量3.2.1 常量的使用 3.3 C#数据类型3.3.1 数据类型之值类型3.3.2 数据类型之引用类型 更多C#基础知识详解请查看:C#基础知识 …...
Java面向对象思想以及原理以及内存图解
文章目录 什么是面向对象面向对象和面向过程区别创建一个对象用什么运算符?面向对象实现伪代码面向对象三大特征类和对象的关系。 基础案例代码实现实例化创建car对象时car引用的内存图对象调用方法过程 成员变量和局部变量作用范围在内存中的位置 关于对象的引用关系简介相关…...
Gitbook----基于 Windows 10 系统本地安装配置 Gitbook 编写属于自己的电子书
查看原文 文章目录 一、安装 Nodejs二、安装 Gitbook三、gitbook 的使用方法四、设计电子书的目录结构五、设置 gitbook 常用配置 一、安装 Nodejs 若要在 Windows 10 系统即本地使用 Gitbook,需要安装 gitlab-cli 工具,而 gitbook-cli 工具是基于 Node…...
springMVC-Restful风格
基本介绍 REST:即Representational State Transfer。(资源)表现层状态转化。是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用. 1.HTTP协议里面,四个表示操…...
【OS】操作系统总复习笔记
操作系统总复习 文章目录 操作系统总复习一、考试题型1. 论述分析题2. 计算题3. 应用题 二、操作系统引论(第1章)2.1 操作系统的发展过程2.2 操作系统定义2.3 操作系统的基本特性2.3.1 并发2.3.2 共享2.3.3 虚拟2.3.4 异步 2.4 OS的功能2.5 OS结构2.5 习…...
powerbuilder游标的使⽤
在某些PowerBuilder应⽤程序的开发中,您可能根本⽤不到游标这样⼀个对象。因为在其它⼯具开发中很多需⽤游标实现的⼯作,在PowerBuilder中却已有DataWin-dow来代劳了。事实上,DataWindow不仅可以替代游标进⾏从后台数据库查询多条记录的复杂操作,⽽且还远不⽌这些。但是同DataW…...
docker创建镜像 Dockerfile
目录 docker的创建镜像的方式 dockerfile形成(原理) docker的核心作用 docker的文件结构 dockerfile的语法 CMD和ENTRPOINT的区别 创建dockerfile镜像 区别 RUN命令的优化 如何把run命令写在一块 copy和ADD区别 区别 centos7 构建Apache的d…...
C++共享和保护——(2)生存期
归纳编程学习的感悟, 记录奋斗路上的点滴, 希望能帮到一样刻苦的你! 如有不足欢迎指正! 共同学习交流! 🌎欢迎各位→点赞 👍 收藏⭐ 留言📝 生命如同寓言,其价值不在于…...
你好,C++(3)2.1 一个C++程序的自白
第2部分 与C第一次亲密接触 在浏览了C“三分天下”的世界版图之后,便对C有了基本的了解,算是一只脚跨入了C世界的大门。那么,怎样将我们的另外一只脚也跨入C世界的大门呢?是该即刻开始编写C程序?还是…… 正在我们犹…...
【INTEL(ALTERA)】Agilex7 FPGA Development Kit DK-DEV-AGI027R1BES编程/烧录/烧写/下载步骤
DK-DEV-AGI027R1BES 的编程步骤: 将外部 USB Blaster II 连接到 J10- 外部 JTAG 接头。将交换机 SW5.3 设置为 ON(首次)。打开 英特尔 Quartus Prime Pro Edition 软件编程工具。单击 硬件设置 ,然后选择 USB Blaster II。将硬件…...
大文件分块上传的代码,C++转delphi,由delphi实现。
在 Delphi 中,我们通常使用 IdHTTP 或 TNetHTTPClient 等组件来处理 HTTP 请求 原文章链接: 掌握分片上传:优化大文件传输的关键策略 【C】【WinHttp】【curl】-CSDN博客 改造思路: 文件分块处理:使用 TFileStream 来…...
MongoDB表的主键可以重复?!MongoDB的坑
MongoDB表的主键可以重复?! 眼见为实? 碰到一个奇怪的现象, MongoDB的一个表居然有两个一样的_id值! 再次提交时,是会报主键冲突的。那上图,为什么会有两个一样的_id呢? 将它们的…...
C++初阶-list类的模拟实现
list类的模拟实现 一、基本框架1.1 节点类1.2 迭代器类1.3 list类 二、构造函数和析构函数2.1 构造函数2.2 析构函数 三、operator的重载和拷贝构造3.1 operator的重载3.2 拷贝构造 四、迭代器的实现4.1 迭代器类中的各种操作4.1 list类中的迭代器 五、list的增容和删除5.1 尾插…...
RecyclerView中的设计模式解读
一.观察者模式:(待完善,这个写的不咋地,没理解透彻) 1.观察者模式的概念: (1)消息传递方向:被观察者->观察者 (2)代码实现: 首…...
ACwing算法备战蓝桥杯——Day30——树状数组
定义: 树状数组是一种数据结构,能将对一个区间内数据进行修改和求前缀和的这两种操作的最坏时间复杂度降低到O(logn); 实现所需变量 变量名变量数据类型作用数组a[]int存储一段区间数组tr[]int表示树状数组 主要操作 函数名函数参数组要作用lowbit()int…...
elementui + vue2实现表格行的上下移动
场景: 如上,要实现表格行的上下移动 实现: <el-dialogappend-to-bodytitle"条件编辑":visible.sync"dialogVisible"width"60%"><el-table :data"data1" border style"width: 100%&q…...
2、快速搞定Kafka术语
快速搞定Kafka术语 Kafka 服务端3层消息架构 Kafka 客户端Broker 如何持久化数据小结 Kafka 服务端 3层消息架构 第 1 层是主题层,每个主题可以配置 M 个分区,而每个分区又可以配置 N 个副本。第 2 层是分区层,每个分区的 N 个副本中只能有…...
CSS新手入门笔记整理:CSS3选择器
属性选择器 属性选择器,指的是通过“元素的属性”来选择元素的一种方式。 语法 元素[attr^"xxx"]{} 元素[attr$"xxx"]{} 元素[attr*"xxx"]{} 选择器 说明 E[attr^"xxx"] 选择元素E,其中E元素的attr属性是…...
D34|不同路径
62.不同路径 初始思路: 1)确定dp数组以及下标的含义: dp[i][i]存放到第i1行和第i1列的方法数 2)确定递推公式: dp[i][i] dp[i -1][i] dp[i][i-1] 3)dp数组如何初始化 第0行是1; 第0列是1&a…...
【运维】Kafka高可用: KRaft(不依赖zookeeper)集群搭建
文章目录 一. kafka kraft 集群介绍1. KRaft架构2. Controller 服务器3. Process Roles4. Quorum Voters5. kraft的工作原理 ing 二. 集群安装1. 安装1.1. 配置1.2. 格式化 2. 启动测试2.1. 启功节点服务2.2. 测试 本文主要介绍了 kafka raft集群架构: 与旧架构的不…...
Python 自动化之批量处理文件(一)
批量新建目录、文档Pro版本 文章目录 批量新建目录、文档Pro版本前言一、做成什么样子二、基本思路1.引入库2.基本架构 三、用户输入模块四、数据处理模块1.excel表格数据获取2.批量数据的生成 总结 前言 我来写一个不一样的批量新建吧。在工作中,有些同学应该会遇…...
力扣72. 编辑距离
动态规划 思路: 假设 dp[i][j] 是 word1 前 i 个字母到 word2 前 j 个字母的编辑距离;那么状态 dp[i][j] 状态的上一个状态有: dp[i - 1][j],word1 前 i - 1 个字母到 word2 前 j 个字母的编辑距离,此状态再插入一个字…...
Unity中 URP Shader 的纹理与采样器的分离定义
文章目录 前言一、URP Shader 纹理采样的实现1、在属性面板定义一个2D变量用于接收纹理2、申明纹理3、申明采样器4、进行纹理采样 二、申明纹理 和 申明采样器内部干了什么1、申明纹理2、申明采样器 三、采样器设置采样器的传入格式1、纹理设置中,可以看见我们的采样…...
Electron学习第一天 ,启动项目
之前在安装官网的步骤操作,结果报错,找了好多办法,最后这种办法成功启动项目,并且没有报错,特此记录 特别提醒,最好安装淘宝镜像,npm 太慢,会导致报错问题,解决起来个人觉…...
广发证券 网站谁做的/b站推广软件
keka在创建压缩和解压时从不要求文件名。现在keka总是在拖放文件夹/文件进行压缩时要求新的文件名。这个问题是因为更新了有关文件访问的信息,那么该如何解决,恢复到以前那样? Keka文件访问权限解决办法 磁盘访问 为了能够像以前一样集成&…...
网站建设 英文怎么说/大数据营销精准营销
转载链接:https://blog.csdn.net/Silence_Sep/article/details/81119683 起意 看到一篇不错的博客,想转载但是不会(没有转载键),网搜发现没有Chrome浏览器的详细转载办法,受博主Bily猪启发,弄了…...
合肥模板网站建设费用/如何在各大网站发布信息
////TITLE:// 环境变量和选择性代码编译//AUTHOR:// norains//DATE:// Moday 17-November-2008//Environment:// NONE// 其实这篇文章的完整标题应该是:《PLATFORM BUILDER中的环境变量设置和选择性代码编译》,只是这样似乎有点冗长了。:…...
怎样注册自己网站/sem 优化价格
小白系统免费的人工客服点击联系上期文章发出后,发现还是有很多的朋友执着于win7,是哪几位小伙伴我就不艾特你了!所以今天小白就给大家带来一篇win10如何退回到win7的教程,认真看,认真学!方法一:…...
青海建设厅通报网站/seo提升关键词排名
作者简介李剑,携程系统研发部技术专家,负责Redis和Mongodb的容器化和服务化工作,喜欢深入分析系统疑难杂症。前言随着携程的应用大规模在生产上用容器部署,各种上规模的问题都慢慢浮现,其中比较难定位和解决的就是偶发…...
南充网站建设与维护/百度搜索关键词排名
表现:vue项目中,进入页面的时候,报错如下 原因:我把Echarts 的初始化,写到了created 的回调函数中了, 解决办法:将初始化写到mounted的回调函数中 理由: created: 在模板渲染成htm…...