《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 已经被创建,但还未注册到EventLoopChannelRegistered:Channel 已经被注册到了EventLoopChannelActive: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 分配一个EventLoop。在当前实现中,使用顺序循环的方式进行分配以获取一个均衡的分布。
一旦一个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 实现。