ChannelHandler

ChannelHandler

本章包括

  • ChannelPipeline
  • ChannelHandlerContext
  • ChannelHandler
  • Inbound 、outbound比较

接收连接或者创建连接在你的应用的一部分。这些任务着实很重要,但是好存在另外一个更为复杂的部分,需要写更多的代码。这就是处理进入或者流出的数据。
Netty为你提供了很强大的方式来达到这一点。允许用户绑定到ChannelHandler来处理数据。使得ChannelHandler更加强大的是你可以将ChannelHandler串联起来来完成小的任务。这帮助你写干净的、可重用的实现。
你只可以使用ChannelHandler做一件事情,那就是处理数据。你可以压制IO操作,例如一个写请求。所有的这些都可以动态完成,这使得它更加强大。

ChannelPipeline

ChannelPipeline是一串ChannelHandler实例,可以处理或者拦截通道的流入和流出操作。ChannelPipeline提供了拦截过滤链模式,对于事件的处理完全交给用户处理;以及ChannelPipeline中的ChannelHandler如何交互。
对于每一个新的通道,一个新的ChannelPipeline被创建,并且附加到通道。一旦附加,通道和ChannelPipeline之间的耦合是永久的;通道不可能将另外一个ChannelPipeline附加到它上面,或者将ChannelPipeline从当前通道上移除。所有这些都在动处理,你不需要在意这些。
图6.1描述了ChannelPipeline中的ChannelHandler处理IO的典型过程。一个IO操作可以被ChannelInboundHandler或者ChannelOutboundHandler处理,紧接着,流入的IO会调用ChannelInboundInvoker的其中之一方法传递到最近的处理器,流出的IO会调用ChannelOutboundInvoker的其中之一方法传递到最近的处理器,ChannelPipeline继承了它俩。(当前版本使用ChannelHandlerContext处理这些工作)

ChannelPipeline
图6.1 ChannelPipeline
就像图6.1中显示的那样,ChannelPipeline是一连串的ChannelHandler。如果一个流入IO事件触发,它从ChannelPipeline的开始传递到结尾。如果一个流出IO事件触发,它从ChannelPipeline的结尾传递到开始。ChannelPipeline自身通过检核ChannelHandler类型知道它是否可以处理事件。如果它不能处理,它将跳过ChannelHandler,使用下一个匹配的ChannelHandler。
ChannelPipeline上的修改可以动态的完成,这意味着你可以添加、移除、替换ChannelHandler,甚至可以在另外一个ChannelHandler中执行,在自身中移除自己都行。这样允许书写灵活的逻辑,例如多路转接器,我会在本章后面进行更加详细的探索。
现在我们先看看如何修改ChannelPipeline。
表6.1 修改ChannelPipeline的方法

名称描述
addFirst(…)向ChannelPipeline中添加一个ChannelHandler
addBefore(…)
addAfter(…)
addLast(…)
remove(…)从ChannelPipeline中移除一个ChannelHandler
replace(…)替换ChannelPipeline中的一个ChannelHandler为另一个ChannelHandler

以下代码显示如何使用这些方法来修改ChannelPipeline。
列表6.1 修改ChannelPipeline

ChannelPipeline pipeline = ..;
FirstHandler firstHandler = new FirstHandler(); #1
pipeline.addLast(“handler1“, firstHandler); #2
pipeline.addFirst(“handler2“, new SecondHandler()); #3
pipeline.addLast(“handler3“, new ThirdHandler()); #4
pipeline.remove(“handler3“); #5
pipeline.remove(firstHandler); #6
pipeline.replace(“handler2“, “handler4“, new FourthHandler()); #7
#1 创建一个FirstHandler实例
#2 将FirstHandler实例添加到ChannelPipeline中
#3 在第一个位置添加SecondHandler实例,这意味着它会在FirstHandler之前
#4 在最后位置添加ThirdHandle实例到ChannelPipeline中
#5 通过名称移除ThirdHandler实例
#6 通过引用移除FirstHandler实例
#7 将名字为handler2实例用FourthHandler实例替代,名字为handler4

修改ChannelPipeline很容易,你可以根据需求添加,删除替换ChannelHandler。



ChannelHandler执行和阻塞


通常来说添加到ChannelPipeline中的ChannelHandler会处理通过IO线程传递进来的事件,这意味着你不能阻塞,否则你会阻塞IO线程,从而影响全局的IO处理。
有些时候可能由于历史API的原因你可能需要阻塞。例如JDBC。在这种用例场景下,Netty允许你在调用ChannelPipeline.add*方法时候传递EventExecutorGroup,这样时间将会包含在EventExecutorGroup中的一个EventExecutor处理,从而可以继续前进。Netty中的一个默认实现是DefaultEventExecutorGroup。


除了允许ChannelPipeline进行修改操作为,还允许你访问添加在ChannelPipeline里的ChannelHandler实现,还可以检验ChannelPipeline中是否存在指定的ChannelHandler。
表6.2 ChannelPipeline中的获取操作

名称描述
get(…)ChannelPipeline提供了一些get(…)方法,这些方法允许你获取ChannelHandler,或者获取创建并且分配给ChannelHandler的ChannelHandlerContext
context(…)返回绑定到ChannelHandler上的ChannelHandlerContext
contains(…)检核在ChannelPipeline中是否包含ChannelHandler,通过实例或者名字
names()
iterator()返回添加到ChannelPipeline中的所有的名字或者实例

因为ChannelPipeline继承自ChannelInboundInvoker和ChannelOutboundInvoker,它暴露了额外的方法来触发进入和流出操作。
表6.3列出了所有的暴露的流入操作,这些定义在ChannelInboundInvoker接口中。除了ChannelPipeline,ChannelHandlerContext 继承自ChannelInboundInvoker,也暴露了这些接口。
列表6.3 ChannelPipeline中的流入操作

名称描述
fireChannelRegistered()结果会使得ChannelPipeline中的下一个ChannelInboundHandler的channelRegistered(ChannelHandlerContext)方法调用
fireChannelUnregistered()结果会使得ChannelPipeline中的下一个ChannelInboundHandler的channelUnregistered(ChannelHandlerContext)方法调用
fireChannelActive()结果会使得ChannelPipeline中的下一个ChannelInboundHandler的channelActive(ChannelHandlerContext)方法调用
fireChannelInactive()结果会使得ChannelPipeline中的下一个ChannelInboundHandler的channelInactive(ChannelHandlerContext)方法调用
fireExceptionCaught(…)结果会使得ChannelPipeline中的下一个ChannelInboundHandler的exceptionCaught(ChannelHandlerContext,Throwable)方法调用
fireUserEventTriggered(…)结果会使得ChannelPipeline中的下一个ChannelInboundHandler的userEventTriggered(ChannelHandlerContext,Object)方法调用
fireChannelRead(….)结果会使得ChannelPipeline中的下一个ChannelInboundHandler的channelRead(ChannelHandlerContext, Object msg)方法调用
fireChannelReadComplete()结果会使得ChannelPipeline中的下一个ChannelInboundHandler的channelReadComplete(ChannelHandlerContext)方法调用

这些方法主要用于通知ChannelPipeline中的ChannelInboundHandler来处理不同的事件。
处理流入事件只完成了一半的事情。你同样需要触发和处理流出事件,它们会导致底层的套接字操作。
列表6.4暴露了所有的ChannelOutboundInvoker接口中的所有流出操作。除了ChannelPipeline,ChannelHandlerContext和Channel也继承与ChannelOutboundInvoker。
列表6.4 ChannelPipeline中的流出操作

名称描述
bind(…)请求通道绑定到本地地址,这会调用ChannelPipeline中的下一个ChannelOutboundHandler的bind(ChannelHandlerContext, SocketAddress, ChannelPromise)方法
connect(…)请求连接到远程地址,这会调用ChannelPipeline中的下一个ChannelOutboundHandler的connect(ChannelHandlerContext, SocketAddress,ChannelPromise)方法
disconnect(…)请求断链,这会调用ChannelPipeline中的下一个ChannelOutboundHandler的disconnect(ChannelHandlerContext, ChannelPromise)方法
close(…)请求关闭通道,这会调用ChannelPipeline中的下一个ChannelOutboundHandler的close(ChannelHandlerContext, ChannelPromise)方法
deregister(…)请求去除通道在EventLoop中注册,这会调用ChannelPipeline中的下一个ChannelOutboundHandler的deregister(ChannelHandlerContext, ChannelPromise)方法
flush(…)请求通道的所有写入,这会调用ChannelPipeline中的下一个ChannelOutboundHandler的flush(ChannelHandlerContext)方法
write()请求向通道中写入数据,这会调用ChannelPipeline中的下一个ChannelOutboundHandler的write(ChannelHandlerContext, Object msg, ChannelPromise)方法
writeAndFlush(…)
writeAndFlush(…)write(…)和flush(…)快捷写法
read()请求从通道中读入更多数据,这会调用ChannelPipeline中的下一个ChannelOutboundHandler的read(ChannelHanlderContext) 方法

ChannelHandlerContext

每一次ChannelHandler被添加到ChannelPipeline,一个ChannelHandlerContext会被创建和分配。ChannelHandlerContext允许一个ChannelHandler与另外一个ChannelHandler实现,在基础传输的尾部,它们必须属于同一个ChannelPipeline。
一个ChannelHandler添加时候的ChannelHandlerContext从不变化,所以将ChannelHandlerContext保存下来是安全的。
ChannelHandlerContext实现了ChannelInboundInvoker和ChannelOutboundInvoker接口。ChannelHandlerContext有一些方法在Channel和ChannelPipeline中也有。不同点是你调用Channel和ChannelPipeline中的方法会流经整个ChannelPipeline。相反。你调用ChannelHandlerContext上的方法,会从当前点开始,会通知ChannelPipeline中离得最近的ChannelHandler来处理事件。

通知下一个ChannelHandler

你可以通过调用ChannelInboundInvoker 和 ChannelOutboundInvoker中的一个方法来通知ChannelPipeline中的最近的ChannelHandler。通知起始于哪里取决于你建立的通知。
图6.2 展示了属于ChannelHandler的ChannelHandlerContext,以及将它绑定到ChannelPipeline中。
ChannelPipeline、ChannelHandlerContext和Channel
图6.2 ChannelPipeline、ChannelHandlerContext和Channel

#1 ChannelPipeline 绑定的 Channel
#2 ChannelPipeline 持有添加的 ChannelHandler 实例
#3 ChannelHandler 是 ChannelPipeline 的组成部分
#4 当添加ChannelHandler 到 ChannelPipeline的时候,创建了 ChannelHandlerContext 

如果你想让事件流遍整个ChannelPipeline,存在两种不同的方式:
ƒ - 调用Channel上的方法
- ƒ 调用ChannelPipeline上的方法

两个方法都会使得事件流遍整个ChannelPipeline。从起始处还是结尾处开始依赖于事件的特性。如果它是流入事件,那么它开始于起始处,如果它是流出事件,它开始于结尾处。以下列表展示了如何传递一个写入事件到ChannelPipeline的结尾处开始(它是一个流出操作):

列表6.2 通过Channel的事件

ChannelHandlerContext ctx = ..;
Channel channel = ctx.channel(); #A
channel.write(Unpooled.copiedBuffer(“Netty in Action“,
CharsetUtil.UTF_8)); #B
#A 获取属于ChannelHandlerContext的通道的引用
#B 通过通道写入缓存

这个消息流经整个ChannelPipeline。前面提及过,你同样可以通过ChannelPipeline完成。 T下个列表展示了这些。
列表6.3 通过ChannelPipeline的事件

ChannelHandlerContext ctx = ..;
ChannelPipeline pipeline = ctx.pipeline(); #A
pipeline.write(Unpooled.copiedBuffer(“Netty in Action“,
CharsetUtil.UTF_8)); #B
#A 获取属于 ChannelHandlerContext 的ChannelPipeline 实例
#B 通过 ChannelPipeline 写入缓存

这个消息流经整个ChannelPipeline。 列表 6.2 和6.3中的每一个操作都等价。你应该注意到 Channel 和
ChannelPipeline 通过 ChannelHandlerContext获取。图6.3 展示了通过Channel 或者 ChannelPipeline触发的事件通知流程。
通过Channel 和ChannelPipeline的通知
图6.3 通过Channel 和ChannelPipeline的通知

#1 事件传递给ChannelPipeline中的第一个ChannelHandler 
#2 ChannelHandler通过分配给它的ChannelHandlercontext将事件传递给 ChannelPipeline中的下一个ChannelHandlercontext
#3 ChannelHandler通过分配给它的ChannelHandlercontext将事件传递给 ChannelPipeline中的下一个ChannelHandlercontext

也许有些场景你需要从ChannelPipeline中一些特定位置开始,而不是流经整个ChannelPipeline,例如:

  • 为了提前保存,特意的跳过一些不感兴趣的ChannelHandler
  • 排除一些ChannelHandler

ƒ
ƒ
在这些场景下,你可以使用你想开始的ChannelHandler的ChannelHandlerContext 。注意执行的是下一个 ChannelHandler 而不是属于ChannelHandlerContext的ChannelHandler 。
列表6.4 展示了通过直接使用ChannelHandlerContext 进行的操作。
Listing 6.4 通过ChannelPipeline的事件

ChannelHandlerContext ctx = ..; #1
ctx.write(Unpooled.copiedBuffer(“Netty in Action“, CharsetUtil.UTF_8)); #2
#1 获取ChannelHandlerContext引用
#2 通过ChannelHandlerContext写入缓存

消息通过ChanneHandlerContext 的下一个ChannelHandler 流经ChannelPipeline 。在这种情况下,事件流会开始于ChannelHandlerContext的下一个ChannelHandler 。
图6.4 展示了事件流
通过ChannelHandlerContext触发的事件流
图6.4通过ChannelHandlerContext触发的事件流

#1 使用ChannelHandlerContext将事件传递给特定的ChannelHandler
#2 事件传递
#3 没有剩余ChannelHandler时流出ChannelPipeline

如你所见,开始于指定的ChannelHandlerContext ,跳过在它前面的所有ChannelHandler。使用 ChannelHandlerContext进行操作是一种最普遍的方式,也是在ChannelHandler实现中使用最多的操作。你同样可以在外部使用ChannelHandlerContext ,因为它是线程安全的。

修改ChannelPipeline

你可以通过调用ChannelHandler 的pipeline() 方法访问ChannelHandler属于的 ChannelPipeline 。一个简单的应用可以在运行时动态的插入,移除或者替换ChannelPipeline 中的ChannelHandler。


注意你可以保存ChannelHandlerContext 在后面使用,例如在处理方法的外面触发事件,甚至从不同的线程中。


下面展示了你可以保存ChannelHandlerContext 在后面使用,紧接着使用它,甚至在另外一个线程。
**列表 6.5 ChannelHandlerContext 的使用

public class WriteHandler extends ChannelHandlerAdapter {
private ChannelHandlerContext ctx;
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
this.ctx = ctx; #A
}
public void send(String msg) { #B
ctx.write(msg);
}
}
#A 保存ChannelHandlerContext 的应用到后面使用
#B 使用先前存储的ChannelHandlerContext 发送消息

请注意一个ChannelHandler有@Sharable注解,那么它的 实例可以被添加到多个
ChannelPipeline。 这意味着一个单独的ChannelHandler 实例可以拥有大于一个ChannelHandlerContext,因此一个实例可以由不同的ChannelHandlerContext触发。

如果一个ChannelHandle,没有@Sharable注解,你将它添加到大于一个ChannelPipeline中,将会抛出异常。请注意,如果 ChannelHandler 有@Sharable注解,那么从不同线程中使用它就必须安全,同时在不同的通道中使用它也必须是安全的。让我们来看看应该如何使用它。以下显示了正确使用@Sharable注解。

列表 6.6 @Sharable的合法使用

@Sharable #A
public class SharableHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println(“Channel read message “ + msg); #B
ctx.fireChannelRead(msg);
}
}
#A 使用@Sharable注解
#B 记录方法调用,导向到下一个 ChannelHandler

这里的使用是合法的,因为ChannelHandler 没有存储任何字段,所以它是无状态的。.
同样存在@Sharable的错误使用,如下表所示。
列表 6.7 @Sharable的错误使用


@Sharable #1
public class NotSharableHandler extends ChannelInboundHandlerAdapter {
private int count;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
count++; #2
System.out.println(“channelRead(...) called the “
+ count + “ time“); #3
ctx.fireChannelRead(msg();
}
}
#1 使用@Sharable注解
#2 增加count字段
#3 记录方法调用,导向到下一个 ChannelHandler

为什么这里的@Sharable使用是错误的?看到这些代码应该很容易猜出来。问题是我们使用了一个字段来保存方法调用的次数。当你将同一个NotSharableHandler 添加到不同的ChannelPipeline 中,会起到副作用,因为同一个字段同时被不同的连接(可能是多个线程)访问修改。
使用@Sharable的原则是在不同的通道上可以复用ChannelHandler 。


为什么需要共享 ChannelHandler ?

你可能想问为什么需要共享ChannelHandler ,使用@Sharable注解,着实存在一些场景需要。
首先共享它你需要创建更少的对象,减少垃圾回收。另一个使用场景是你想在ChannelHandler 中维护全局的统计,这些统计信息来源于不同的通道(例如,并发连接数目)。


状态模型

Netty拥有简单但是强大的状态模型,这些完美的映射到 ChannelInboundHandler 方法中。我们将在本章的后面看看ChannelInboundHandler 。在表格6.5 中显示了4中不同的状态。
表格 6.5 通道的生命周期状态

状态描述
channelUnregistered通道已经创建,但是它没有注册到EventLoop
channelRegistered通道注册到EventLoop中
channelActive通道活跃,意味着它连接到远程端点,现在可以接收和发送数据。
channelInactiveT通道没有连接到远程节点

通道的状态在生命周期内变化,所以状态变化被触发。通常你可以在Channel的生命周期内看到四个状态变化,如图6.5所示。
状态模型
图6.5 状态模型
在一些高级的场景,你可能看到一些额外的状态变化。因为允许用户手动从EventLoop中注销Channel,来暂停它上面的执行,到后来再重新注册。
在这种场景下,你会看到大于一个channelRegistered 和
channelUnregistered 状态变化。你指挥看到一个
channelActive 和channelInactive状态, 因为一个通道只能用于一个连接生命周期,之后要回收。如果你想重新连接,你需要重新创建。
图6.6 展示了用户从EventLoop 上注销通道,然后再次注册。
高级状态模型
图 6.6 高级状态模型
在本章后面你会学习更多的执行于ByteBuf 上的操作。现在我们记住这些,来复习最可能使用的不同种类的ByteBuf 。(不知所云)

ChannelHandlers and their types

Netty支持通过ChannelHandler拦截操作或者响应状态变化。这样你很容易定制自己的处理逻辑。目前Netty支持两种不同类型的ChannelHandler,如图表6.6中所示。
Table 6.6 ChannelHandler 类型

类型描述
Inbound Handler处理流入数据(接收数据)和所有种类的状态变化
Outbound Handler处理流出数据(发送数据)和允许拦截各种各样的操作

我会讨论每一种类型,我们先看看它们的基础接口。

ChannelHandler—所有处理器的父类

Netty使用定义良好的继承机制来代表不同的处理器类型。所有处理器的父类是ChannelHandler。它提供了ChannelHandler 从ChannelPipeline中添加或者移除所触发的生命周期操作。
Table 6.7 ChannelHandler 方法

类型描述
handlerAdded(…)
handlerRemoved(…)ChannelHandler 从ChannelPipeline中添加或者移除时候这些方法被调用
exceptionCaught(…)在ChannelPipeline中的处理出现错误时被调用

表格6.7中的方法被调用的时候会接收一个ChannelHandlerContext 作为参数。这个ChannelHandlerContext 是为添加到ChannelPipeline中的ChannelHandler 自动创建的。这个ChannelHandlerContext绑定到 ChannelHandler, ChannelPipeline, 和Channel 本身。

这个ChannelHandlerContext 允许你安全的存储和获取值,对于Channel来说它是本地的。请参照ChannelHandlerContext 相关章节来了解更多关于如何操作它。

Netty 提供了ChannelHandler 的一个框架实现叫做ChannelHandlerAdapter。它为所有的方法提供了基础实现,所以你只需要实现(覆写)你感兴趣的方法。基础实现是将事件传递给下一个ChannelHandler直到ChannelPipeline的末端。

Inbound handlers

Inbound handlers处理流入时间和状态变化。如果你想响应事情满足这些条件,选择它就对了。在这一部分,我们将探索不同的ChannelHandler 子类型实现来处理流入逻辑。
CHANNELINBOUNDHANDLER
ChannelInboundHandler 提供了一些方法,当状态变化或者数据被接收到的时候这些方法会被调用。这些方法映射到6.3节中所阐述的通道状态模型。表格6.8列出了ChannelInboundHandler 方法。
Table 6.8 ChannelInboundHandler methods

类型描述
channelRegistered(…)当Channel被注册到 EventLoop时候会被调用, 现在可以处理 I/O了
channelUnregistered(…)当Channel被从 EventLoop中注销时候会被调用, 现在不可以处理 I/O了
channelActive(…)Channel 活跃意味着Channel出于连接状态,准备就绪
channelInactive(…)Channel出于非连接状态
channelReadComplete(…)Channel 上的读操作完成之后调用
channelRead(…)在流入缓存中存在可读数据时候被调用
userEventTriggered(…)用户通过定制对象触发事件的时候被调用

这里的每一个方法都可以被ChannelInboundInvoker调用,ChannelInboundInvoker会被ChannelHandlerContext 和
ChannelPipeline继承。
ChannelInboundHandler是ChannelHandler子类型, 暴露了它所有的方法。
Netty 提供了ChannelInboundHandler 的一个框架实现ChannelInboundHandlerAdapter。这个基础实现实现了所有的方法,并且允许你覆盖你感兴趣的方法。所有的这些方法的默认实现通过调用ChannelHandlerContext中的相同方法将事件传给 ChannelPipeline 中的下一个ChannelInboundHandler 。

注意到ChannelInboundHandler 处理接收消息,所以channelRead(…) 负责释放资源。这个特别重要,因为Netty针对ByteBuf 使用池化资源,如果你忘记释放资源将会导致资源泄漏。

列表 6.8 丢弃数据处理器




@Sharable
public class DiscardHandler extends ChannelInboundHandlerAdapter { #1
@Override
public void channelRead(ChannelHandlerContext ctx,
Object msg) {
ReferenceCountUtil.release(msg); #2
}
}
#1 继承ChannelInboundHandlerAdapter
#2 通过将接收的消息传递给ReferenceCountUtil.release( )丢弃数据

忘记将将接收的消息传递给ReferenceCountUtil.release( )将会导致资源泄漏。
幸运的是Netty会以警告级别记录忘记释放的资源,所以查看你什么时候忘记该操作很容易。

因为手动的释放资源很繁琐,所以出现了SimpleChannelInboundHandler,它为你做了这些事情。这样你就完全不用关心它。 The only important thing here is to remember that if you use
当使用SimpleChannelInboundHandler时候,有件重要的事情要记住,消息在处理之后会被立即释放,所以你一定不要存储它的引用到后来使用。

那么上一个例子应该如何改变? 列表 6.9 展示了相同的实现,但是使用的是SimpleChannelInboundHandler。

列表 6.9 丢弃数据处理器

@Sharable
public class SimpleDiscardHandler
extends SimpleChannelInboundHandler<Object> {
#1
@Override
public void channelRead0(ChannelHandlerContext ctx,
Object msg) {
// No need to do anything special #2
}
}
#1 继承SimpleChannelInboundHandler
#2 丢弃接收的消息但不需要释放任何资源

如果你想在其它状态变化时候被通知,你覆盖改变处理器的其它的方法。

通常你想将字节码解析为定制的消息类型,你也许想着实现ChannelInboundHandler 接口或者继承ChannelInboundhandlerAdapter。但是,请稍等一下,存在更好的解决方案。这些功能通过编码框架很容易实现,我在本章的后面会详细介绍。现在,我们来复习一下ChannelHandler。

你是使用ChannelInboundHandler, ChannelInboundHandlerAdapter 或者SimpleChannelInboundhandler 依赖于你的需求。 大多数情况下你使用SimpleChannelInboundHandler 处理消息, 使用ChannelInboundHandlerAdapter 处理器其它的流入事件或者状态变化。


流入消息处理和引用计数


从前面的一些章节中我们知道Netty使用引用计数来处理池化的ByteBuf。因此在ByteBuf 被完全处理之后调整引用计数很重要。正因为这样,了解ChannelOutboundHandlerAdapter和SimpleChannelInboundHandler的不同点很重要。 ChannelInboundHandlerAdapter在消息channelRead(…) 执行完之后不会释放消息,所以在消息消费之后,由用户负责释放。SimpleChannelInboundHandler在每个channelRead(…) 调用之后会自动释放消息,或者消费你的消息,要么调用retain() 在你方法返回之后调用消息。
消息的不正确释放会导致内存泄漏,幸运的是Netty默认记录。


CHANNELINITIALIZER
有一个细微修改的ChannelInboundHandler 需要注意:ChannelInitializer。它所完成的和它名字所强调的那样。它允许你在Channel注册到EventLoop,IO处理就绪的时候初始化Channel。ChannelInitializer 主要用于构建每一个Channel的ChannelPipeline 。这是引导的一部分(在第9章,我们会深入看看)。现在我们只要记住存在一个额外的ChannelInboundHandler 。

Outbound handlers

你已经学习了ChannelHandler 如何绑定到流入操作和数据,现在我们该看看ChannelHandler 的实现如何绑定到流出操作和数据。这一部分向你展示全部。

CHANNELOUTBOUNDHANDLER
ChannelOutboundHandler 提供了一些方法,当流出操作被请求的时候调用。这些方法在ChannelOutboundInvoker接口 中都有列出,它被 Channel, ChannelPipeline和ChannelHandlerContext扩展。

ChannelOutboundHandler 强大原因是实现它所提供的能力和延迟请求中操作。这里提供了一些强大、灵活的方式处理请求。例如,你可以延迟冲刷操作,如果没有任何可写的东西,到后面你可以再次冲刷。

表6.9 展示了支持的所有方法
Table 6.9 ChannelOutboundHandler 方法

类型描述
bind(…)通道绑定本地地址请求发起的时候触发
connect(…)通道连接到远程端点请求发起的时候触发
disconnect(…)通道断开到远程的连接请求发起的时候触发
close(…)关闭通道请求发起的时候触发
deregister(…)Channel 从 EventLoop 中注销的请求时候触发
read(…)从通道中读取数据请求发起的时候触发
flush(…)冲刷数据到远程端点的请求发起的时候触发
write(…)消息写到远程节点的通道的时候触发

ChannelOutboundHandler 是 ChannelHandler的子类型 ,它暴露了父类的所有方法。

几乎所有的方法都会使用ChannelPromise 作为参数,一旦请求停止了继续向前进行,它就会被通知。

Netty 提供了 ChannelOutboundHandler的一个实现叫做ChannelOutboundHandlerAdapter。它提供了所有方法的基础实现,并且允许你覆盖你感兴趣的实现。所有的默认实现通过调用ChannelHandlerContext中相同的方法将事件传递给ChannelPipeline 中的下一个ChannelOutboundHandler。

这里和ChannelInboundHandler一样。如果你处理写操作,求其消息,由你负责释放它。

现在我们看看如何在实战中使用它。下面的列表展示丢弃所有写数据的实现。
Listing 6.10 丢弃流出数据的处理器




@Sharable
public class DiscardOutboundHandler
extends ChannelOutboundHandlerAdapter { #1
@Override
public void write(ChannelHandlerContext ctx,
Object msg, ChannelPromise promise) {
ReferenceCountUtil.release(msg);
#2
promise.setSuccess(); #3
}
}
#1 继承ChannelOutboundHandlerAdapter
#2 使用ReferenceCountUtil.release( )释放资源
#3 通知ChannelPromise数据已处理

释放资源,通知ChannelPromise很重要。如果ChannelPromise不被通知可能导致ChannelFutureListener不被通知处理的消息。


流出消息处理和引用计数

如果一个消息被消费掉,不被传递给ChannelPipleline中的下一个ChannelOutboundHandler ,用户负责调用ReferenceCountUtil.release(message)。一旦消息传递到真正的传输层,消息被写入或者通道被关闭,它会自动释放。
从这个例子中你可以看到ChannelHandler 的实现和相关的特征使得使用Netty更加简单和高效。


总结

在本章你深入的看到了Netty如何通过ChannelHandler 实现来完成数据处理的。本章展示了ChannelHandler 如何串联起来以及ChannelPipeline 如何使用它们。
本章还强调了流入和流出处理器的不同点,还有处理消息以及处理字节等不同类型的不同点。
在下一章节,我会集中于Netty中的编解码抽象,它使得使用编解码框架比使用原始的ChannelHandler 接口更加简单。同样,我会深入的介绍如何更容易的测试你的ChannelHandler 实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值