Netty 提供 异步的、事件驱动
的网络应用程序框架和工具。
作为一个异步框架,Netty 的所有 IO 操作都是异步非阻塞的
,通过 Future-Listener 机制,用户可以方便地 主动获取 或者 通过通知机制 获得 IO 操作结果
。
API 使用简单,开发门槛低。
功能强大,预置了 多种编解码功能,支持多种主流协议
。
定制能力强,可以通过 ChannelHandler 对通信框架进行灵活扩展
。
性能高,与其他业界主流的 NIO 框架对比,Netty 的综合性能最优。
成熟、稳定,Netty 修复了已经发现的所有 JDK NIO 中的 BUG
,业务开发人员不需要再为 NIO 的 BUG 而烦恼。
社区活跃,版本迭代周期短,发现的 BUG 可以被及时修复。
channel -> pipeline -> handlers
每个 Channel 内部都有一个 pipeline,pipeline 由多个 handler 组成, handler 之间的顺序是很重要的, 因为 IO 事件将按照顺序顺次经过 pipeline 上的 handler
ChannelFactory.newChannel() 方法的调用时机
对于 NioSocketChannel, 由于它充当客户端的功能, 它的创建时机在 connect(…) 的时候;
对于 NioServerSocketChannel 来说, 它充当服务端功能, 它的创建时机在绑定端口 bind(…) 的时候.
Promise 实例内部是一个任务, 任务的执行往往是异步的, 通常是一个线程池来处理任务.
Promise 提供的 setSuccess(V result) 或 setFailure(Throwable t) 将来会被某个执行任务的线程在执行完成以后调用,
同时那个线程在调用 setSuccess(result) 或 setFailure(t) 后会回调 listeners 的回调函数 (当然, 回调的具体内容不一定要由执行任务的线程自己来执行, 它可以创建新的线程来执行, 也可以将回调任务提交到某个线程池来执行).
而且, 一旦 setSuccess(…) 或 setFailure(…) 后, 那些 await() 或 sync() 的线程就会从等待中返回.
所以这里就有两种编程方式,
一种是用 await(), 等 await() 方法返回后, 得到 promise 的执行结果, 然后处理它;
另一种就是提供 Listener 实例, 我们不太关心任务什么时候会执行完, 只要它执行完了以后会去执行 listener 中的处理方法就行.
在 Netty 中,IO 事件被分为 Inbound 事件和 Outbound 事件
Outbound 的 out 指的是 出去, 有哪些 IO 事件属于此类呢?比如 connect,write,flush 这些 IO 操作是往外部方向进行的, 它们就属于 Outbound 事件.
其他的, 诸如 accept,read 这种就属于 Inbound 事件.
ch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
ch.pipeline().addLast("decoder", new StringDecoder());// Inbound
ch.pipeline().addLast("encoder", new StringEncoder());// Outbound
ch.pipeline().addLast(new EchoServerHandler());// Inbound
对于 Inbound 操作, 按照添加顺序执行每个 Inbound 类型的 handler; 而对于 Outbound 操作, 是反着来的, 从后往前, 顺次执行 Outbound 类型的 handler.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pNlJamUI-1596045520578)(…/…/assets/2019-05-13-14-15-55.png)]
一个 Channel 关联一个 pipeline
pipeline 现在的样子, head + channelInitializer + tail
首先, 我们说的 Netty 的 线程池
, 指的就是 NioEventLoopGroup
的实例;
线程池中的 单个线程
, 指的是 NioEventLoop
的实例.
对于 Buffer 来说, 另一个常见的操作中就是, 我们要将来自 Channel 的数据填充到 Buffer 中, 在系统层面上, 这个操作我们称为读操作, 因为数据是从外部 (文件或网络等) 读到内存中.
int num = channel.read(buf);
上述方法会返回从 Channel 中读入到 Buffer 的数据大小
从 Buffer 中读出来写入 Channel
通过 SocketChannel 将数据写入网络发送到远程机器等. 对应的, 这种操作, 我们称之为写操作.
int num = channel.write(buf);
Channel 经常翻译为通道, 类似 IO 中的流, 用于读取和写入. 它与前面介绍的 Buffer 打交道,
读操作的时候将 Channel 中的数据填充到 Buffer 中,
而写操作时将 Buffer 中的数据写入到 Channel 中.
NIO 三大组件就剩 Selector 了,
Selector 建立在非阻塞的基础之上,
大家经常听到的 多路复用 在 Java 世界中指的就是它,用于实现一个线程管理多个 Channel
.
将 Channel 注册到 Selector 上. 前面我们说了, Selector 建立在非阻塞模式之上, 所以注册到 Selector 的 Channel 必须要支持非阻塞模式
// 将通道设置为非阻塞模式, 因为默认都是阻塞模式的
channel.configureBlocking(false);
// 注册
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
多进程处理
对于应用服务器, 一个主要规律就是, CPU 的处理速度是要远远快于 IO 速度的, 如果 CPU 为了 IO 操作 (例如从 Socket 读取一段数据) 而阻塞显然是不划算的.
好一点的方法是分为多进程或者线程去进行处理, 但是这样会带来一些进程切换的开销
回调
这时先驱们找到了事件驱动, 或者叫回调的方式, 来完成这件事情. 这种方式就是, 应用业务向一个中间人注册一个回调 (event handler), 当 IO 就绪后, 就这个中间人产生一个事件, 并通知此 handler 进行处理.
Reactor
在前面事件驱动的例子里有个问题:
我们如何知道 IO 就绪这个事件, 谁来充当这个中间人?
Reactor 模式的答案是: 由一个不断等待和循环的单独进程 (线程) 来做这件事, 它接受所有 handler 的注册, 并负责先操作系统查询 IO 是否就绪, 在就绪后就调用指定 handler 进行处理, 这个角色的名字就叫做 Reactor.
NIO 中 Reactor 的核心是 Selector
public void run() {
try {
while(!Thread.interrupted()) {
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while(it.hasNext())
dispatch((SelectionKey)(it.next()));
selected.clear();
}
} catch(IOException ex) {
/* ... */
}
}
NIO 的 Selector 有三个 select() 方法, 它们的区别如下:
select() 阻塞直到有一个感兴趣的 IO 事件就绪
select(long timeout) 与 select() 类似, 但阻塞的最长时间为给定的 timeout
selectNow() 不会阻塞, 直接返回而不管是否有 IO 事件就绪
wakeUp() 方法, 其功能是唤醒一个阻塞在 select() 上的线程, 使其继续运行
本来 select 操作的代码不会这么复杂,
主要是由于 JDK BUG 导致 select() 方法并不阻塞而直接返回且返回值为 0, 从而出现空轮询使 CPU 完全耗尽.
Netty 解决的办法是: 对 select 返回 0 的操作计数, 如果次数大于阈值 SELECTOR_AUTO_REBUILD_THRESHOLD 就新建一个 selector, 将注册到老的 selector 上的 channel 重新注册到新的 selector 上.
阈值 SELECTOR_AUTO_REBUILD_THRESHOLD 可由用户使用系统变量 io.netty.selectorAutoRebuildThreshold 配置, 默认为 512. 这里注意 for()循环中大量使用了 break, 含有 break 的部分才是关键操作, 其他部分 (其实就只有一处) 是为了解决 JDK BUG.
https://www.jianshu.com/p/a06da3256f0c
NioEventLoop
--> SingleThreadEventLoop
--> SingleThreadEventExecutor
--> AbstractScheduledEventExecutor
--> AbstractEventExecutor
--> AbstractExecutorService
NioEventLoopGroup
--> MultithreadEventLoopGroup
--> MultithreadEventExecutorGroup
--> AbstractEventExecutorGroup
Netty 使用了相对时间调度, 时间起点为 ScheduledFutureTask 类第一次被类加载器加载的时间
当调用 shutdownGracefully() 时, 线程状态改变为 ST_SHUTTING_DOWN; 调用 shutdown() 时, 线程状态改变为 ST_SHUTDOWN
Future 接口中的方法都是 getter 方法而没有 setter 方法, 也就是说这样实现的 Future 子类的状态是不可变的, 如果我们想要变化, 那该怎么办呢?Netty 提供的解决方法是: 使用可写的 Future 即 Promise
AbstractFuture
<--
CompleteFuture
<--
CompleteChannelFuture
<--
Succeeded/FailedChannelFuture
DefaultPromise
<--
DefaultChannelPromise
由于 CompleteFuture 表示一个已完成的异步操作, 所以可推知 sync() 和 await() 方法都将立即返回, 线程状态为 isDone() = true; isCancelled() = false;
Unsafe 的子类作为 Channel 的内部类, 负责处理底层 NIO 相关的 I/O 事件. Channel 则使用责任链的方式通过 ChannelPipeline 将事件提供给用户自定义处理.
NioEventLoopGroup 构造方法中, 创建 NioEventLoop, 创建