文章目录
- 前言
- 一、ChannelHandler
- 二、ChannelInboundHandler
- 三、ChannelOutboundHandler
- 四、ChannelDuplexHandler
- 总结
前言
前两篇文章我们已经对Netty进行了简单的了解和架构设计原理的剖析。
相关文章链接如下:
- Netty 概述(一)
- Netty 架构设计(二)
- Netty Channel 概述(三)
先简略了解一下ChannelPipeline和ChannelHandler的概念。
想象一个流水线车间。当组件从流水线头部进入,穿越流水线,流水线上的工人按顺序对组件进行加工,到达流水线尾部时商品组装完成。可以将ChannelPipeline当做流水线,ChannelHandler当做流水线工人。源头的组件当做event,如read,write等等。
本篇文章我们先来讲讲ChannelHandler的相关知识,下面进入正文吧。
一、ChannelHandler
- ChannelHandler并不处理事件,而由其子类代为处理:ChannelInboundHandler拦截和处理入站事件,ChannelOutboundHandler拦截和处理出站事件。
- ChannelHandler和ChannelHandlerContext通过组合或继承的方式关联到一起成对使用。事件通过ChannelHandlerContext主动调用如fireXXX()和write(msg)等方法,将事件传播到下一个处理器。
注意:入站事件在ChannelPipeline双向链表中由头到尾正向传播,出站事件则方向相反。
ChannaleHandler 作为最顶层的接口,并不处理入站和出站事件,所以接口中只包含最基本的方法:
// Handler本身被添加到ChannelPipeline时调用
void handlerAdded(ChannelHandlerContext var1) throws Exception;
// Handler本身被从ChannelPipeline中删除时调用
void handlerRemoved(ChannelHandlerContext var1) throws Exception;
// 发生异常时调用
@Deprecated
void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;
Sharable注解:
当客户端连接到服务器时,Netty新建一个ChannelPipeline处理其中的事件,而一个ChannelPipeline中含有若干ChannelHandler。如果每个客户端连接都新建一个ChannelHandler实例,当有大量客户端时,服务器将保存大量的ChannelHandler实例。为此,Netty提供了Sharable注解,如果一个ChannelHandler状态无关,那么可将其标注为Sharable,如此,服务器只需保存一个实例就能处理所有客户端的事件。
@Inherited
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sharable {
}
作为ChannelHandler的默认实现,ChannelHandlerAdapter有个重要的方法isSharable(),代码如下:
public boolean isSharable() {
Class<?> clazz = this.getClass();
// 每个线程一个缓存
Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
Boolean sharable = (Boolean)cache.get(clazz);
if (sharable == null) {
// Handler是否存在Sharable注解
sharable = clazz.isAnnotationPresent(Sharable.class);
cache.put(clazz, sharable);
}
return sharable;
}
这里引入了优化的线程局部变量InternalThreadLocalMap,即每个线程都有一份ChannelHandler是否Sharable的缓存。这样可以减少线程间的竞争,提升性能。
二、ChannelInboundHandler
ChannelInboundHandler处理入站数据以及各种状态变化,当Channel状态发生改变会调用ChannelInboundHandler中的一些生命周期方法。这些方法与Channel的生命密切相关。
入站数据,就是进入socket的数据。下面展示一些该接口的生命周期API:
类型 | 描述 |
---|---|
channelRegistered | 当 Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用 |
channelUnregistered | 当 Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调用 |
channelActive | 当 Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪 |
channelInactive | 当 Channel 离开活动状态并且不再连接它的远程节点时被调用 |
channelReadComplete | 当Channel上的一个读操作完成时被调用 |
channelRead | 当从 Channel 读取数据时被调用 |
ChannelWritabilityChanged | 当Channel的可写状态发生改变时被调用。用户可以确保写操作不会完成得太快(以避免发生 OutOfMemoryError)或者可以在 Channel 变为再次可写时恢复写入。可以通过调用Channel的isWritable()方法来检测Channel 的可写性。与可写性相关的阈值可以通过Channel.config().setWriteHighWaterMark()和 Channel.config().setWriteLowWaterMark()方法来设置 |
userEventTriggered | 当 ChannelnboundHandler.fireUserEventTriggered()方法被调用时被调用,因为一个 POJO 被传经了 ChannelPipeline |
ChannelInboundHandlerAdapter作为ChannelInboundHandler的实现,默认将入站事件自动传播到下一个入站处理器。其中的代码高度一致,如下:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
三、ChannelOutboundHandler
出站操作和数据将由 ChannelOutboundHandler 处理。它的方法将被 Channel、 ChannelPipeline 以及 ChannelHandlerContext 调用。 ChannelOutboundHandler 的一个强大的功能是可以按需推迟操作或者事件,这使得可以通过一些复杂的方法来处理请求。
例如, 如果到远程节点的写入被暂停了, 那么你可以推迟冲刷操作并在稍后继续。
同理,ChannelOutboundHandlerAdapter作为ChannelOutboundHandler的事件,默认将出站事件传播到下一个出站处理器:
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
ctx.read();
}
四、ChannelDuplexHandler
ChannelDuplexHandler则同时实现了ChannelInboundHandler和ChannelOutboundHandler接口。如果一个所需的ChannelHandler既要处理入站事件又要处理出站事件,推荐继承此类。
总结
以上就是关于ChannelHandler的分析,相信你对ChannelHandler也有一定的了解,下期我们再来分析ChannelPipeline的源码。