channel接口
channel接口的作用是连接buffer和Selector,可以认为channel就是socket,每个channel都会分配一个ChannelPipeLine和channelconfig。同时,每个channelconfig包含了channel的所有配置设置,且支持热更新。
channelpipeline,可以认为是channel通向Selector的桥梁或者管道。
channelPipeline持有所有出和入的数据,以及处理器 channelhandler的实例。
channel图解
channel的方法
一般Server 用 NioServerSocketChannel
Clinet使用NioSocketChannel
channel的生命周期
channelhandler
channelhandler实现了大量功能:
1 将数据从一种格式转化为另一种格式;
2 提供channel变为活动或者非活动的通知;
3 提供异常的通知;
4 提供有关用户自定义的通知;
5 提供channel注册到EventLoop的通知;
针对不同类型的事件来调用ChannelHandler;
应用程序通过实现或者扩展ChannelHandler 来挂钩到事件的生命周期,并且提供自
定义的应用程序逻辑;
在架构上,ChannelHandler 有助于保持业务逻辑与网络处理代码的分离。这简化了开发过程,因为代码必须不断地演化以响应不断变化的需求。
从应用程序开发人员的角度来看,Netty 的主要组件是ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。这是可行的,因为ChannelHandler 的方法是由网络事件(其中术语“事件”的使用非常广泛)触发的。事实上,ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另外一种格式,或者处理转换过程中所抛出的异常。
编码解码
当你通过Netty 发送或者接收一个消息的时候,就将会发生一次数据转换。入站消息会被解码;也就是说,从字节转换为另一种格式,通常是一个Java 对象。如果是出站消息,则会发生相反方向的转换:它将从它的当前格式被编码为字节。这两种方向的转换的原因很简单:网络数据总是一系列的字节。
ByteToMessageDecoder 或MessageToByte
Encoder
所有由Netty 提供的编码器/解码器适配器类都实现了ChannelOutboundHandler 或者ChannelInboundHandler 接口。
你将会发现对于入站数据来说,channelRead 方法/事件已经被重写了。对于每个从入站Channel 读取的消息,这个方法都将会被调用。随后,它将调用由预置解码器所提供的decode()方法,并将已解码的字节转发给ChannelPipeline 中的下一个ChannelInboundHandler。
出站消息的模式是相反方向的:编码器将消息转换为字节,并将它们转发给下一个ChannelOutboundHandler。
SimpleChannelInboundHandler
最常见的情况是,你的应用程序会利用一个ChannelHandler 来接收解码消息,并对该数据应用业务逻辑。要创建一个这样的ChannelHandler,你只需要扩展基SimpleChannelnboundHandler,其中T 是你要处理的消息的Java 类型。在这个ChannelHandler 中,你将需要重写基类的一个或者多个方法,并且获取一个到ChannelHandlerContext 的引用,这个引用将作为输入参数传递给ChannelHandler 的所有方法。在这种类型的ChannelHandler 中, 最重要的方法是channelRead0(ChannelHandlerContext,T)。除了要求不要阻塞当前的I/O 线程之外,其具体实现完全取决于你
ChannelInboundHandler的事件
当某个ChannelInboundHandler 的实现重写channelRead()方法时,它将负责显式地释放与池化的ByteBuf 实例相关的内存。Netty 为此提供了一个实用方法ReferenceCountUtil.release()
Sharable
public class DiscardHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ReferenceCountUtil.release(msg);//丢弃已接收的消息
ChannelOutboundHandler
出站操作和数据将由ChannelOutboundHandler 处理。它的方法将被Channel、ChannelPipeline 以及ChannelHandlerContext 调用。
ChannelOutboundHandler 的一个强大的功能是可以按需推迟操作或者事件,这使得可以通过一些复杂的方法来处理请求。例如,如果到远程节点的写入被暂停了,那么你可以推迟冲刷操作并在稍后继续
ChannelPromise
ChannelOutboundHandler中的大部分方法都需要一个ChannelPromise参数,以便在操作完成时得到通知。ChannelPromise是ChannelFuture的一个子类,其定义了一些可写的方法,如setSuccess()和setFailure()
消息的释放
总之,如果一个消息被消费或者丢弃了,并且没有传递给ChannelPipeline 中的下一个ChannelOutboundHandler,那么用户就有责任调用ReferenceCountUtil.release()。
最后一个负责ReferenceCountUtil.release(msg);
如果消息到达了实际的传输层,那么当它被写入时或者Channel 关闭时,都将被自动释放。
ChannelPipeline
如果你认为ChannelPipeline是一个拦截流经Channel的入站和出站事件的ChannelHandler 实例链,那么就很容易看出这些ChannelHandler 之间的交互是如何组成一个应用程序数据和事件处理逻辑的核心的。
每一个新创建的Channel 都将会被分配一个新的ChannelPipeline。这项关联是永久性的;Channel 既不能附加另外一个ChannelPipeline,也不能分离其当前的。在Netty 组件的生命周期中,这是一项固定的操作,不需要开发人员的任何干预。
ChannelHandlerContext
ChannelHandlerContext使得ChannelHandler能够和它的ChannelPipeline以及其他的ChannelHandler 交互。ChannelHandler 可以通知其所属的ChannelPipeline 中的下一个ChannelHandler,甚至可以动态修改它所属的ChannelPipeline。
ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之间的关联,每当有 ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandlerContext。ChannelHandlerContext 的主要功能是管理它所关联的 ChannelHandler 和在同一个 ChannelPipeline 中的其他
ChannelHandler 之间的交互
ChannelHandlerContext 有很多的方法,其中一些方法也存在于Channel 和ChannelPipeline 本身上,但是有一点重要的不同。如果调用Channel 或者ChannelPipeline 上的这些方法,它们将沿着整个ChannelPipeline 进行传播。而调用位于ChannelHandlerContext上的相同方法,则将从当前所关联的ChannelHandler 开始,并且只会传播给位于该ChannelPipeline 中的下一个能够处理该事件的ChannelHandler。
当使用ChannelHandlerContext 的API 的时候,请牢记以下两点:
ChannelHandlerContext 和ChannelHandler 之间的关联(绑定)是永远不会改变的,所以缓存对它的引用是安全的;
如同我们在本节开头所解释的一样,相对于其他类的同名方法,ChannelHandler Context的方法将产生更短的事件流,应该尽可能地利用这个特性来获得最大的性能。
实现动态的协议切换
SIX POINT THREE POINT TWO
优雅的设计
ResourceLeakDetector
Netty提供了class ResourceLeakDetector,它将对你应用程序的缓冲区分配做大约1%的采样来检测内存泄露。相关的开销是非常小的。
如果检测到了内存泄露,将会产生类似于下面的日志消息:
LEAK: ByteBuf.release() was not called before it’s garbage-collected. Enableadvanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option ‘-Dio.netty.leakDetectionLevel=ADVANCED’ or call Resourc eLeakDetector.setLevel().
4 种泄漏检测级别
抓异常
要想处理这种类型的入站异常,你需要在你
的ChannelInboundHandler 实现中重写下面的方法。
public void exceptionCaught(
Cha nnelHandlerContext ctx, Throwable cause) throws Exception
public class InboundExceptionHandler extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
因为异常将会继续按照入站方向流动(就像所有的入站事件一样),所以实现了前面所示逻辑的ChannelInboundHandler 通常位于ChannelPipeline 的最后。这确保了所有的入站异常都总是会被处理,无论它们可能会发生在ChannelPipeline 中的什么位置。
你应该如何响应异常,可能很大程度上取决于你的应用程序。你可能想要关闭Channel(和连接),也可能会尝试进行恢复。如果你不实现任何处理入站异常的逻辑(或者没有消费该异常),那么Netty将会记录该异常没有被处理的事实
总结一下:
ChannelHandler.exceptionCaught()的默认实现是简单地将当前异常转发给ChannelPipeline 中的下一个ChannelHandler;
如果异常到达了ChannelPipeline 的尾端,它将会被记录为未被处理;
要想定义自定义的处理逻辑,你需要重写exceptionCaught()方法。然后你需要决定是否需要将该异常传播出去。
EventLoop
作用是控制流、多线程处理、并发
channel注册到EventLoop中
1 一个EventLoop在它的生命周期内,只有一个Thread绑定;
2 一个channel,在他的生命周期内,只能注册于一个EventLoop上;
channelFuture
Netty 中所有的I/O 操作都是异步的。因为一个操作可能不会
立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此,Netty 提供了
ChannelFuture 接口,其addListener()方法注册了一个ChannelFutureListener,以
便在某个操作完成时(无论是否成功)得到通知。
Netty的操作都是基于异步的,一个操作可能不会立刻返回,于是,就需要一个接收异步通知的方法。
Netty:用ChannelFuture的addListener方法注册一个ChannelFutureListener,得到监听结果。
ByteBuf
什么是ByteBuf?
本质就是字节容器
下面是一些ByteBuf API 的优点:
它可以被用户自定义的缓冲区类型扩展;
通过内置的复合缓冲区类型实现了透明的零拷贝;
容量可以按需增长(类似于JDK 的StringBuilder);
在读和写这两种模式之间切换不需要调用ByteBuffer 的flip()方法;
读和写使用了不同的索引;
支持方法的链式调用;
支持引用计数;
支持池化。
ByteBufHolder
我们经常发现,除了实际的数据负载之外,我们还需要存储各种属性值。HTTP 响应便是一个很好的例子,除了表示为字节的内容,还包括状态码、cookie 等。
为了处理这种常见的用例,Netty 提供了ByteBufHolder。ByteBufHolder 也为Netty 的高级特性提供了支持,如缓冲区池化,其中可以从池中借用ByteBuf,并且在需要时自动释放。
ByteBufHolder 只有几种用于访问底层数据和引用计数的方法。表5-6 列出了它们(这里不包括它继承自ReferenceCounted 的那些方法)。
ByteBufAllocator
为了降低分配和释放内存的开销,Netty 通过interface ByteBufAllocator 实现了(ByteBuf 的)池化,它可以用来分配我们所描述过的任意类型的ByteBuf 实例。使用池化是特定于应用程序的决定,其并不会以任何方式改变ByteBuf API(的语义)。
可以通过Channel(每个都可以有一个不同的ByteBufAllocator 实例)或者绑定到
ChannelHandler 的ChannelHandlerContext 获取一个到ByteBufAllocator 的引用.
ByteBufAllocator allocator = channel.alloc();
....
ChannelHandlerContext ctx = ...;
ByteBufAllocator allocator2 = ctx.alloc();
Unpooled
可能某些情况下,你未能获取一个到ByteBufAllocator 的引用。对于这种情况,Netty 提供了一个简单的称为Unpooled 的工具类,它提供了静态的辅助方法来创建未池化的ByteBuf实例
ByteBufUtil
ByteBufUtil 提供了用于操作ByteBuf 的静态的辅助方法。因为这个API 是通用的,并且和池化无关,所以这些方法已然在分配类的外部实现。
这些静态方法中最有价值的可能就是hexdump()方法,它以十六进制的表示形式打印ByteBuf 的内容。这在各种情况下都很有用,例如,出于调试的目的记录ByteBuf 的内容。十六进制的表示通常会提供一个比字节值的直接表示形式更加有用的日志条目,此外,十六进制的
版本还可以很容易地转换回实际的字节表示。
另一个有用的方法是boolean equals(ByteBuf, ByteBuf),它被用来判断两个ByteBuf实例的相等性。如果你实现自己的ByteBuf 子类,你可能会发现ByteBufUtil 的其他有用方法。
解码编码
将字节解码为消息——ByteToMessageDecoder 和ReplayingDecoder;
将一种消息类型解码为另一种——MessageToMessageDecoder。
如果您觉得我的付出对您有帮助,还请关注+点赞哦