《Netty实战》读书笔记——Netty的概念及体系结构
Netty 的核心组件
Channel
可以把 Channel 看作是传入(入站)或者传出(出站)数据的载体。因此,它可以被打开或者被关闭,连接或者断开连接。
回调
一个回调其实就是一个方法,一个指向已经被提供给另外一个方法的方法的引用。这使得后者可以在适当的时候调用前者。回调在广泛的编程场景中都有应用,而且也是在操作完成后通知相关方最常见的方式之一。
Future
Future 提供了另一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问。
ChannelFuture
Netty 提供了它自己的实现—— ChannelFuture
,用于在执行异步操作的时候使用。
ChannelFuture
提供了几种额外的方法,这些方法使得我们能够注册一个或者多个ChannelFutureListener
实例。
由ChannelFutureListener
提供的通知机制消除了手动检查对应的操作是否完成的必要。
每个 Netty 的出站 I/O 操作都将返回一个ChannelFuture
,也就是说,它们都不会阻塞。
事件和Channel Handler
Netty 使用不同的事件来通知我们状态的改变或者是操作的状态。
每个事件都可以被分发给ChannelHandler
类中的某个用户实现的方法。
ChannelPipeline
中的每个ChannelHandler
将负责把事件转发到链中的下一个ChannelHandler
。这些适配器类(及它们的子类)将自动执行这个操作,所以你可以只重写那些你想要特殊处理的方法和事件。
虽然ChannelInboundHandler
和ChannelOutboundHandlr
都扩展自ChannelHandler
,但是Netty 能区分ChannelInboundHandler
实现和ChannelOutboundHandler
实现,并确保数据只会在具有相同定向类型的两个ChannelHandler
之间传递。
@Sharable
注解Handler,标识一个Handler可以被多个Channel安全地共享。
发送消息的方式
在Netty中,有两种发送消息的方式。你可以直接写到Channel中,也可以写到和ChannelHandler
相关联的ChannelHandlerContext
对象中。前一种方式将会导致消息从ChannelPipeline
的尾端开始流动,而后者将导致消息从ChannelPipeline
中的下一个ChannelHandler
开始流动。
写入Channel
通过ChannelHandlerContext
获取到Channel的引用。调用Channel上的write()
方法将会导致写入事件从尾端到头部地流经ChannelPipeline
。
ChannelHandlerContext ctx = ...;
Channel channel = ctx.channel();
channel.write(...);
ChannelHandlerContext ctx = ...;
ChannelPipeline pipeline ctx.pipeline();
pipeline.write(...);
写入ChannelHandlerContext
ChannelHandlerContext ctx = ...;
ctx.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));
使用NIO
使用epoll 替代NIO,只需要将NioEventLoopGroup
替换为EpollEventLoopGroup
, 并且将NioServerSocketChannel.class
替换为EpollServerSocketChannel.class
即可。
Netty所提供的传输:
应用程序的最佳传输:
派生缓冲区
派生缓冲区为ByteBuf 提供了以专门的方式来呈现其内容的视图。
duplicate()
slice()
slice(int, int)
Unpooled.unmodifiableBuffer()
order(ByteOrder)
readSlice(int)
channel生命周期
ChannelUnregistered
:Channel 已经被创建,但还未注册到EventLoop
ChannelRegistered
:Channel 已经被注册到了EventLoop
ChannelActive
:Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接收和发送数据了ChannelInactive
:Channel 没有连接到远程节点
Channel的状态模型:
ChannelHandler 的生命周期
handlerAdded
:当把ChannelHandler
添加到ChannelPipeline
中时被调用handlerRemoved
:当从ChannelPipeline
中移除ChannelHandler
时被调用exceptionCaught
:当处理过程中在ChannelPipeline
中有错误产生时被调用
Netty 定义了下面两个重要的ChannelHandler
子接口:
ChannelInboundHandler
:处理入站数据以及各种状态变化ChannelOutboundHandler
:处理出站数据并且允许拦截所有的操作
ChannelInboundHandler的方法:
一个更加简单的方式是使用SimpleChannelInboundHandler
:
由于SimpleChannelInboundHandler
会自动释放资源,所以你不应该存储指向任何消息的引用供将来使用,因为这些引用都将会失效。
出站
如果一个消息被消费或者丢弃了,并且没有传递给ChannelPipeline
中的下一个
ChannelOutboundHandler
,那么用户就有责任调用ReferenceCountUtil.release()
。
如果消息到达了实际的传输层,那么当它被写入时或者Channel
关闭时,都将被自动释放。
不仅要释放资源,还要通知ChannelPromise
。
promise.setSuccess();
否则可能会出现ChannelFutureListener
收不到某个消息已经被处理了的通知的情况。
ChannelOutboundHandler的方法:
在ChannelPipeline
传播事件时,它会测试ChannelPipeline
中的下一个ChannelHandler
的类型是否和事件的运动方向相匹配。如果不匹配,ChannelPipeline
将跳过该ChannelHandler
并前进到下一个,直到它找到和该事件所期望的方向相匹配的为止。
ChannelHandler
的用于修改ChannelPipeline
的方式:
addFirst
、addBefore
、addAfter
、addLast
---> 将一个ChannelHandler添加到ChannelPipeline中remove
、replace
ChannelPipeline
的入站操作:fireXXXX
ChannelPipeline
的出站操作,和ChannelOutboundHandler
基本一致。
ChannelPipeline
保存了与Channel
相关联的ChannelHandler
;
ChannelPipeline
可以根据需要,通过添加或者删除ChannelHandler
来动态地修改;
ChannelPipeline
有着丰富的API 用以被调用,以响应入站和出站事件。
ChannelHandlerContext
和ChannelHandler
之间的关联(绑定)是永远不会改变的,所以缓存对它的引用是安全的;
相对于其他类的同名方法,ChannelHandlerContext
的方法将产生更短的事件流,应该尽可能地利用这个特性来获得最大的性能。
Channel、ChannelPipeline、ChannelHandler以及ChannelHandlerContext之间的关系:
要想调用从某个特定的ChannelHandler
开始的处理过程,必须获取到在该ChannelHandler
之前的ChannelHandler
所关联的ChannelHandlerContext
。这个ChannelHandlerContext
将调用和它所关联的ChannelHandler
之后的ChannelHandler
。
异常处理
处理入站异常:exceptionCaught
处理出站异常
每个出站操作都将返回一个ChannelFuture
。注册到ChannelFuture
的ChannelFutureListener
将在操作完成时被通知该操作是成功了还是出错了。
ChannelFuture future = channel.write(msg);
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) {
//...
几乎所有的ChannelOutboundHandler
上的方法都会传入一个ChannelPromise
的实例。
作为ChannelFuture
的子类,ChannelPromise
也可以被分配用于异步通知的监听器。但是,ChannelPromise
还具有提供立即通知的可写方法:
ChannelPromise setSuccess();
ChannelPromise setFailure(Throwable cause);
public class OutboundExceptionHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
promise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) {
//...
EventLoop
一个EventLoop
将由一个永远都不会改变的Thread
驱动。
提供了一个更加简单的执行体系架构,并且消除了在多个ChannelHandler
中进行同步的需要
任务调度
稍后执行:
ScheduledFuture<?> future = ch.eventLoop().schedule(new Runnable() {
@Override
public void run() {
System.out.println("60 seconds later");
}
}, 60, TimeUnit.SECONDS);
用于非阻塞传输(如NIO和AIO)的EventLoop
分配方式:
上图显示了一个EventLoopGroup
,它具有3 个固定大小的EventLoop
(每个EventLoop
都由一个Thread
支撑)。在创建EventLoopGroup
时就直接分配了EventLoop
(以及支撑它们的Thread
),以确保在需要时它们是可用的。
EventLoopGroup
负责为每个新创建的Channel
分配一个EventLoo
p。在当前实现中,使用顺序循环的方式进行分配以获取一个均衡的分布。
一旦一个Channel
被分配给一个EventLoop
,它将在它的整个生命周期中都使用这个EventLoop
(以及相关联的Thread
)
Bootstrap
Bootstrap
类被用于客户端或者使用了无连接协议的应用程序中。
对于NIO 以及OIO 传输两者来说,都有相关的EventLoopGroup
和Channel
实现。不能混用具有不同前缀的组件,否则将会导致IllegalStateException
ServerBootstrap
和ServerChannel
:
Channel实现
ChannelInitializer
定义了下面的方法:
protect ed abstract void initChannel(C ch) throws Exception;
这个方法提供了一种将多个ChannelHandler
添加到一个ChannelPipeline
中的简便
方法。
Netty提供了各种DatagramChannel
的实现,不再调用connect
方法,而是只调用bind
方法。
EmbeddedChannel
:用于测试ChannelHandler
。这个传输是一种特殊的Channel 实现。