io.netty:netty-all:4.1.33.Final
Channel
代表一个到实体(如一个硬件设备、一个文件、一个Socket或者一个能够执行一个或者多个不同的IO操作的程序组件)的开放连接,如读和写操作。Channel
是对Socket的抽象,Channel
接口提供的API,大大的降低了Socket类的复杂性
可以把Channel
看作是传入或者传出数据的载体,Channel
可以被打开或者被关闭,连接或者断开连接
使用java原生API进行网络编程,当你想从阻塞传输切换到非阻塞传输,这个切换不是那么容易的,反之亦然。Netty在它的传输协议实现上提供了统一的API,使得这种解决方案更简单。传输API的核心是channel
接口,用于所有的outbound
操作.
从上图中可以看到,每个Channel
都将会被分配一个ChannelPipeline
和 ChannelConfig
。ChannelConfig
包含了该Channel
的所有配置设置,并且支持热更新。由于特定的传输可能具有独特的设置,所以它可能会实现一个ChannelConfig
的子类型
Channel
重要方法
方法名称 | 描述 |
---|---|
eventLoop() |
返回分配给Channel 的EventLoop |
pipeline() |
返回分配给Channel 的ChannelPipeline |
isActive() |
如果Channel 是活动的,则返回 true。活动的意义可能依赖于底层的传输。例如,一个Socket传输一旦连接到了远程节点便是活动的,而一个Datagram传输一旦被打开便是活动的 |
localAddress() |
返回绑定到本地的SocketAddress |
remoteAddress() |
返回绑定到远程的SocketAddress |
write() |
将数据写到远程节点。这个数据将被传递给ChannelPipeline , 并且排队直到它被flush |
flush |
将之前已写的数据冲刷到底层传输,如一个Socket |
writeAndFlush |
一个简便的方法,等同于调用 write() 并接着调用flush() |
Netty中Channel
的特性
Channel
是独一无二的Channel
是线程安全的Channel
的生命周期状态
状态 | 描述 |
---|---|
ChannelUnregistered |
Channel 已创建,还未注册到一个EventLoop 上 |
ChannelRegistered |
Channel 已经注册到一个EventLoop 上 |
ChannelActive |
Channel 是活跃状态(连接到某个远端),可以收发数据 |
ChannelInactive |
Channel 未连接到远端 |
当Channel
状态发生变化时,将会生成对应的事件,这些事件会被转发给ChannelPipeline
中的ChannelHandler
ChannelHandler
不仅仅充当所有处理入站和出站数据的应用程序逻辑的容器,而且还可以处理其他的动作,比如将数据从一种格式转换为另外一种格式,或者处理转换过程中所抛出的异常等。ChannelHandler
典型的用法
Channel
变为活动的或者非活动的通知Channel
注册到EventLoop
或者从EventLoop
注销时的通知;ChannelHandler
生命周期
类型 | 描述 |
---|---|
handlerAdded |
当把ChannelHandler 添加到ChannelPipeline 中时被调用 |
handlerRemoved |
当从ChannelPipeline 中移除ChannelHandler 时被调用 |
exceptionCaught |
当处理过程中在ChannelPipeline 中有错误产生时被调用 |
ChannelHandler
接口方法
public interface ChannelHandler {
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Sharable {
// no value
}
}
Netty定义了两个重要的ChannelHandler
子接口
ChannelInboundHandler
:处理入站数据以及各种状态变化ChannelOutboundHandler
:处理出站数据并且允许拦截所有的操作方法
类型 | 描述 |
---|---|
channelRegistered |
当Channel 已经注册到它的EventLoop 并且能够处理I/O时被调用 |
channelUnregistered |
当Channel 从它的EventLoop 注销并且无法处理任何I/O才被调用 |
channelActive |
当Channel 处于活动状态时被调用,Channel 已经连接/绑定并且已经就绪 |
channelInactive |
当Channel 离开活动状态并且不再连接它的远程节点时被调用 |
channelReadComplete |
当Channel 上的一个读操作完成时被调用,可能在 channelReadComplete() 被调用之前看到多次调用channelRead() |
channelRead |
当从Channel 读取数据时被调用,当某个ChannelInboundHandler 的实现重写了此方法时,需要手动显示的释放与池化ByteBuf实例相关的内存,Netty提供了ReferenceCountUtil.release(msg) 来手动释放 |
ChannelWritabilityChanged |
当Channel 的可写状态发生改变时被调用。 用户可以确保写操作不会完成得太快(以避免发生OutOfMemoryError)或者可以在Channel 变为再次可写时恢复写入。可以通过调用Channel 的isWritable() 方法来检测Channel 的可写性。与可写性相关的阈值可以通过Channel.config().setWriteHighWaterMark() 和 Channel.config().setWriteLowWaterMark() 方法来设置 |
userEventTriggered |
ChannelnboundHandler.fireUserEventTriggered() 方法被调用时被调用,因为一个POJO被传经了ChannelPipeline |
常规的运行顺序
//连接成功
handlerAdded()
channelActive()
//客户端发送数据过来
channelRead()
channelReadComplete()
//连接失效
channelInactive()
channelUnregistered
handlerRemoved()
出站操作和数据将由ChannelOutboundHandler
处理。它的方法将被Channel
、ChannelPipeline
以及ChannelHandlerContext
调用。
ChannelOutboundHandler
的一个强大的功能是可以按需推迟操作或者事件,这使得可以通过一些复杂的方法来处理请求。例如:如果到远程节点的写入被暂停了,那么你可以推迟冲刷操作井在稍后继续。
ChannelOutboundHandler
接口的方法
方法 | 描述 |
---|---|
bind(ChannelHandlerContext, SocketAddress , ChannelPromise) | 当请求将 Channel 绑定到本地地址时被调用 |
connect(ChannelHandlerContext , SocketAddress , SocketAddress , ChannelPromise) | 当请求将 Channel 连接到远程节点时被调用 |
disconnect(ChannelHandlerContext, ChannelPromise) | 当请求将 Channel 从远程节点断开时被调用 |
close(ChannelHandlerContext ctx, ChannelPromise promise) | 当请求关闭 Channel 时被调用 |
deregister(ChannelHandlerContext ctx, ChannelPromise promise) | 当请求将Channel从它的EventLoop注销时被调用 |
read(ChannelHandlerContext) | 当请求从 Channel 读取更多的数据时被调用 |
flush(ChannelHandlerContext) | 当请求通过 Channel 将入队数据冲刷到远程节点时被调用 |
write(ChannelHandlerContext,Object, ChannelPromise) | 当请求通过 Channel将数据写到远程节点时被调用 |
ChannelPromise
与ChannelFuture
ChannelOutboundHandler 中的大部分方法都需要一个ChannelPromise
参数,以便在操作完成时得到通知。ChannelPromise
是 ChannelFuture
的一个子类,其定义了一些可写的方法,如setSuccess()
和 setFailure()
,从而使ChannelFuture
不可变(当一个Promise
被完成之后,其对应的Future
的值便不能再进行任何修改了)
ChannelinboundHandlerAdapter
和ChannelOutboundHandlerAdapter
分别提供了ChannelinboundHandler
与ChannelOutboundHandler
的基本实现
ChannelHandlerAdapter
还提供了实用方法isSharable()
。 如果其对应的实现被标注为@Sharable
,那么这个方法将返回true,表示它可以被添加到多个ChannelPipeline
。当收集跨域多个Channel
的统计信息时,我们可能需要共享通过一个ChannelHandler
当通过调用ChannelinboundHandler.channelRead()
或者ChannelOutboundHandler.write()
方法来处理数据时,都需要确保没有任何的资源泄露。Netty使用引用计数来处理池化的ByteBuf
,所以在完全使用完ByteBuf
后,调整其引用计数很重要。
为了帮助诊断潜在的资源泄露问题,Netty提供了ResourceLeakDetector
,它将对你应用程序的缓冲区分配做大约 1%的采样来检测内存泄露,相关的开销是非常小的。如果检测到内存泄露,会调用reportUntracedLeak
方法打印错误日志
protected void reportUntracedLeak(String resourceType) {
logger.error("LEAK: {}.release() was not called before it's garbage-collected. " +
"Enable advanced leak reporting to find out where the leak occurred. " +
"To enable advanced leak reporting, " +
"specify the JVM option '-D{}={}' or call {}.setLevel() " +
"See http://netty.io/wiki/reference-counted-objects.html for more information.",
resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
}
Netty定义了集中检测级别ResourceLeakDetector.Level
。
级别 | 描述 |
---|---|
DISABLED | 禁用泄漏检测。只有在详尽的测试之后才应设置为这个值 |
SIMPLE | 使用 1%的默认采样率检测并报告任何发现的泄露。这是默认级别,适合绝大部分的情况 |
ADVANCED | 使用默认的采样率,报告所发现的任何的泄露以及对应的消息被访问的位置 |
PARANOID | 类似于 ADVANCED,但是其将会对每次(对消息的)访问都进行来样。这对性能将会有很大的影响,应该只在调试阶段使用 |
内存泄露检测级别可以通过java系统属性来定义java -Dio.netty.leakDetection.level=ADVANCED
或者java -Dio.netty.leakDetectionLevel=ADVANCED
,对应ResourceLeakDetector
类的属性。如果设置新的就以新的为准
//老的
private static final String PROP_LEVEL_OLD = "io.netty.leakDetectionLevel";
//新的
private static final String PROP_LEVEL = "io.netty.leakDetection.level";
// First read old property name
String levelStr = SystemPropertyUtil.get(PROP_LEVEL_OLD, defaultLevel.name());
// If new property name is present, use it
levelStr = SystemPropertyUtil.get(PROP_LEVEL, levelStr);
如果不需要入站数据在channelRead()
中直接消费而不需要转发到下一个ChannelInboundHandler
,可以继承SimpleChannelinboundHandler
并覆写channelRead0()
方法自动释放消息,因为SimpleChannelInboundHandler
自动释放资源,任何对消息的引用都会变成无效,所以你不能保存这些引用待后来使用.
对于出站数据,如果处理了writer()
操作并丢弃一个消息,那么应该负责释放它,并且通知ChannelPromise
数据已经被处理了(否则可能会出现ChannelFutureListener
收不到某个消息已经被处理了的通知的情况)。
public void write(ChannelHandlerContext ctx,Object msg,ChannelPromise promise){
ReferenceCountUtil. release (msg);
promise.setSuccess();
}
ChannelHandler
的实现来处理数据。ChannelPipeline
是ChannelHandler
实例的列表,用于处理或截获Channel
的接收和发送数据。ChannelPipeline
提供了一种高级的截取过滤器模式,让用户可以在ChannelPipeline
中完全控制一个事件及如何处理ChannelHandler
与ChannelPipeline
的交互Channel
被创建时,它会被自动地分配到它专属的ChannelPipeline
。一旦连接,Channel
和ChannelPipeline
的耦合性是永久的,Channel
不能附加其他的ChannelPipeline
或从ChannelPipeline
分离。 每个Channel
都有一个属于自己的ChannelPipeline
,调用Channel#pipeline()
方法可以获得Channel
的ChannelPipeline
,调用Pipeline#channel()
方法可以获得ChannelPipeline
的Channel
。ChannelPipeline
里面就是一个ChannelHandler
的列表。ChannelHandler
的执行顺序是由添加顺序决定的。如果一个入站IO事件被触发,这个事件会从第一个开始依次通过ChannelPipeline
中的ChannelHandler
。若是一个出站I/O事件,则会从最后一个开始依次通过ChannelPipeline
中的ChannelHandler
。ChannelHandler
可以处理事件并检查类型,如果某个ChannelHandler
不能处理则会跳过,并将事件传递到下一个ChannelHandler
。ChannelPipeline
可以动态添加、删除、替换其中的ChannelHandler
,这样的机制可以提高灵活性。ChannelPipeline
保存了与Channel
相关联的ChannelHandler
;ChannelPipeline
可以根据需要,通过添加或者删除ChannelHandler
来动态地修改。例如当业务高峰期需要对系统做拥塞保护时,根据时间判断,动态地将系统拥塞保护ChannelHandler
添加到当前的ChannelPipeline
中。当高峰期过去之后,可以动态删除ChannelPipeline
有着丰富的API用以被调用,以响应入站和出站事件ChannelInboundHandler
添加顺序一样,出站调用顺序与ChannelOutboundHandler
添加顺序相反ChannelPipeline
是线程安全的ChannelHandlerContext
使得ChannelHandler
能够和它的ChannelPipeline
以及其他的ChannelHandler
交互。ChannelHandler
可以通知其所属的ChannelPipeline
中的下一个ChannelHandler
,甚至可以动态修改它所属的 ChannelPipeline
中的ChannelHandler
的编排(顺序)
每当有ChannelHandler
添加到ChannelPipeline
中时,都会创建ChannelHandlerContext
。ChannelHandlerContext
有很多与Channel
和ChannelPipeline
类似的方法,如果调用Channel
或ChannelPipeline
这些方法它们将沿着整个ChannelPipeline
进行传播,而调用位于ChannelHandlerContext
上相同的方法,则将从当前所关联的ChannelHandler
开始,并且只会传播给位于该ChannelPipeline
中的下一个能够处理该事件的ChannelHandler
方法
方法 | 描述 |
---|---|
alloc | 返回和这个实例相关联的 Channel 所配置的 ByteBufAllocator |
bind | 绑定到给定的 SocketAddress,并返回 ChannelFuture |
channel | 返回绑定到这个实例的 Channel |
close | 关闭 Channel,并返回 ChannelFuture |
connect | 连接给定的 SocketAddress,并返回 ChannelFuture |
deregister | 从之前分配的 EventExecutor 注销,并返回 ChannelFuture |
disconnect | 从远程节点断开,并返回 ChannelFuture |
executor | 返回调度事件的 EventExecutor |
fireChannelActive | 触发对下一个 ChannelInboundHandler 上的 channelActive()方法(己连接)的调用 |
fireChannelinactive | 触发对下一个 ChannelInboundHandler 上的 channelInactive()方法(己关闭〉的调用 |
fireChannelRead | 触发对下一个 ChannelInboundHandler 上的 channelRead() 方法(己接收的消息)的调用 |
fireChannelReadComplete | 触发对下一个 ChannelInboundHandler 上的 channelReadComplete ()方法的调用 |
fireChannelRegistered | 触发对下 一个 ChannelInboundHandler 上的fireChannelRegistered ()方法的调用 |
fireChannelUnregistered | 触发对下 一个 ChannelInboundHandler 上的 fireChannelUnregistered ()方法的调用 |
fireChannelWritabilityChanged | 触发对下 一个 ChannelInboundHandler 上的 fireChannelWritabilityChanged ()方法的调用 |
fireExceptionCaught | 触发对下 一个 ChannelInboundHandler 上的 fireExceptionCaught(Throwable)方法的调用 |
fireUserEventTriggered | 触发对下 一个 ChannelInboundHandler 上的 fireUserEventTriggered(Object evt)方法的调用 |
handler | 返回绑定到这个实例的 ChannelHandler |
isRemoved | 如果所关联的 ChannelHandler 已经被从 ChannelPipeline 中移除则返回 true |
name | 返回这个实例的唯一名称 |
pipeline | 返回这个实例所关联的 ChannelPipeline |
read | 将数据从 Channel 读取到第一个入站缓冲区;如果读取成功则触发一个 channelRead事件,并(在最后一个消息被读取完成后)通知 ChannelInboundHandler 的 channelReadComplete(ChannelHandlerContext)方法 |
write | 通过这个实例写入消息并经过 ChannelPipeline |
writeAndFlush | 通过这个实例写入并冲刷消息并经过 ChannelPipeline |
虽然ChannelHandlerContext
可以被用于获取底层的Channel
,但是它主要还是被用于写出站数据。
ChannelHandler
可以通过添加、删除或者替换其他的ChannelHandler
来实时地修改ChannelPipeline
的布局(它也可以将它自己从ChannelPipeline
中移除)
方法 | 描述 |
---|---|
addFirst | 添加ChannelHandler在ChannelPipeline的第一个位置 |
addBefore | 在ChannelPipeline中指定的ChannelHandler名称之前添加ChannelHandler |
addAfter | 在ChannelPipeline中指定的ChannelHandler名称之后添加ChannelHandler |
addLast | 在ChannelPipeline的末尾添加ChannelHandler |
remove | 删除ChannelPipeline中指定的ChannelHandler |
replace | 替换ChannelPipeline中指定的ChannelHandler |
通常ChannelPipeline
中的每一个ChannelHandler
都是通过它的EventLoop
(I/O线程)来处理传递给它的事件的。所以不要阻塞这个线程,因为这会对整体I/O处理产生负面的影响。有的时候可能需要与那些使用阻塞API的遗留代码进行交互,比如JDBC。对于这种情况,ChannelPipeline
有一些接受一个EventExecutorGroup
的add()方法。如果一个事件被传递给一个自定义的EventExecutorGroup
,它将被包含在这个EventExecutorGroup
中的某个EventExecutor
所处理,从而被从该Channel
本身的EventLoop
中移除。对于这种,Netty提供了默认实现DefaultEventExecutorGroup
ChannelPipeline
用于访问Channelhandler
的方法
方法 | 描述 |
---|---|
get | 通过类型或者名称返回ChannelHandler |
context | 返回和ChannelHandler绑定的ChannelHandlerContext |
names | 返回ChannelPipeline中所有的Channelhandler的名称 |
入站操作
方法 | 描述 |
---|---|
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)方法 |
fireChannelWritabilityChanged | 调用 ChannelPipeline 中下一个 ChannelInboundHandler的channelWritabilityChanged(ChannelHandlerCon.text)方法 |
出站操作
方法 | 描述 |
---|---|
bind | 将Channel绑定到一个本地地址,这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler 的 bind(ChannelHandlerContext, SocketAddress , ChannelPromise)方法 |
connect | 将Channel连接到一个远程地址,这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler的connect(ChannelHandlerContext,SocketAddress, ChannelPromise)方法 |
disconnect | 将Channel断开连接。这将调用ChannelPipeline中的下一个ChannelOutboundHandler的disconnect (ChannelHandlerContext,ChannelPromise)方法 |
close | 将 Channel 关闭 。这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler 的 close(ChannelHandlerContext, ChannelPromise)方法 |
deregister | 将 Channel 从它先前所分配的 EventExecutor (即 EventLoop)中注销。这将调用 ChannelPipeline中的下一个ChannelOutboundHandler的deregister(ChannelHandlerContext,ChannelPromise)方法 |
flush | 冲刷 Channel所有挂起的写入。这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler 的 flush(ChannelHandlerContext)方法 |
write | 将消息写入Channel。这将调用 ChannelPipeline中的下一个ChannelOutboundHandler的write(ChannelHandlerContext , Object msg , ChannelPromise)方法。注意:这并不会将消息写入底层的 Socket, 而只会将它放入队列中。 要将它写入 Socket,需要调用 flush ()或者 writeAndFlush()方法 |
writeAndFlush | 这是一个先调用 write ()方法再接着调用 flush ()方法的便利方法 |
read | 请求从 Channel 中读取更多的数据。这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler的read(ChannelHandlerContext)方法 |
从性能角度考虑,为了防止频繁唤醒Selector进行消息发送,Netty的write()方法并不直接将消息写入SocketChannel中,调用write()方法只是把待发送的消息放到发送缓冲数组中,再通过flush方法,将发送缓冲区中的消息全部写到SocketChannel中。