第一章、Netty–异步和事件驱动
Netty包含网络编程、多线程处理和并发。
NIO
NIO 代表非阻塞 I/O(Non-blocking I/O)。
Netty 的核心组件
Netty 的主要构件:
- Channel;
- 回调;
- Future;
- 事件和 ChannelHandler。
Channel
Channel 是 Java NIO 的一个基本构造。
它代表一个到实体(如一个硬件设备、一个文件、一个网络套接字或者一个能够执
行一个或者多个不同的I/O操作的程序组件)的开放连接。
回调
一个回调其实就是一个方法,一个指向已经被提供给另外一个方法的方法的引用。这使得后者可以在适当的时候调用前者。
Future
- Future 提供了在操作完成时通知应用程序的方式。
ChannelFuture提供了几种额外的方法,这些方法使得我们能够注册一个或者多个 ChannelFutureListener实例。
监听器的回调方法operationComplete(),将会在对应的操作完成时被调用。
// Does not block
。然后监听器可以判断该操作是成功地完成了还是出错了。如果是后者,我们可以检索产生的Throwable。简而言之 ,由ChannelFutureListener提供的通知机制消除了手动检查对应的操作是否完成的必要。
每个 Netty 的出站 I/O 操作都将返回一个 ChannelFuture;也就是说,它们都不会阻塞。
事件和 ChannelHandler
Netty 使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作。这些动作可能是:
记录日志、数据转换、流控制、应用程序逻辑。
Netty组件总结
(1)Future、回调和 ChannelHandler。
Netty的异步编程模型是建立在Future和回调的概念之上的,而将事件派发到ChannelHandler。
(2)选择器(Selector)、事件和 EventLoop。
Netty 通过触发事件将 Selector 从应用程序中抽象出来,消除了所有本来将需要手动编写的派发代码。在内部,将会为每个 Channel 分配一个 EventLoop,用以处理所有事件,包括:
注册感兴趣的事件、将事件派发给 ChannelHandler、安排进一步的动作。
EventLoop 本身只由一个线程驱动,其处理了一个 Channel 的所有 I/O 事件,并且在该 EventLoop 的整个生命周期内都不会改变。
第二章 Netty应用程序
-
所有的 Netty 服务器都需要以下两部分:
(1)至少一个 ChannelHandler—该组件实现了服务器对从客户端接收的数据的处理,即它的业务逻辑。
(2)引导—这是配置服务器的启动代码。至少,它会将服务器绑定到它要监听连接请求的端口上。 -
服务器会响应传入的消息,所以它需要实现 ChannelInboundHandler 接口,用来定义响应入站事件的方法。
如果应用程序只需要用到少量的这些方法,所以继承 ChannelInboundHandlerAdapter 类也就足够了,它提供了 ChannelInboundHandler 的默认实现。
channelRead()
—对于每个传入的消息都要调用;
channelReadComplete()
—通知ChannelInboundHandler最后一次对channelRead()的调用是当前批量读取中的最后一条消息;
exceptionCaught()
—在读取操作期间,有异常抛出时会调用。
引导服务器
- 引导服务器本身的过程,具体涉及以下内容:
(1)绑定到服务器将在其上监听并接受传入连接请求的端口;
(2)配置 Channel,以将有关的入站消息通知给 ServerHandler 实例。
服务器的主要代码组件:
- ServerHandler 实现了业务逻辑;
- main()方法引导了服务器;
引导过程中所需要的步骤如下: - 创建一个 ServerBootstrap 的实例以引导和绑定服务器;
- 创建并分配一个 NioEventLoopGroup 实例以进行事件的处理,如接受新连接以及读/写数据;
- 指定服务器绑定的本地的 InetSocketAddress;
- 使用一个 ServerHandler 的实例初始化每一个新的 Channel;
- 调用 ServerBootstrap.bind()方法以绑定服务器。
通过 ChannelHandler 实现客户端逻辑
客户端将拥有一个用来处理数据的 ChannelInboundHandler。可以扩展 SimpleChannelInboundHandler 类以处理所有必须的任务。
要求重写下面的方法:
channelActive()——在到服务器的连接已经建立之后将被调用;
channelRead0()——当从服务器接收到一条消息时被调用;
exceptionCaught()——在处理过程中引发异常时被调用。
引导客户端
- 为初始化客户端,创建了一个 Bootstrap 实例;
- 创建 BootstrapEventLoopGroup 以处理客户端事件;
- 为进行事件处理分配了一个 NioEventLoopGroup 实例,其中事件处理包括创建新的连接以及处理入站和出站数据;
- 为服务器连接创建了一个 InetSocketAddress 实例;
- 当连接被建立时,一个 ClientHandler 实例会被安装到(该 Channel 的)ChannelPipeline 中;
- 在一切都设置完成后,调用 Bootstrap.connect()方法连接到远程节点;
第三章 Netty组件和设计
Channel、EventLoop 和 ChannelFuture
- Channel—Socket;
- EventLoop—控制流、多线程处理、并发;
- ChannelFuture—异步通知。
Channel 接口
基本的 I/O 操作(bind()、connect()、read()和 write())依赖于底层网络传输所提
供的原语。在基于 Java 的网络编程中,其基本的构造是 class Socket。
Netty 的 Channel 接口所提供的 API,大大地降低了直接使用 Socket 类的复杂性。
- EmbeddedChannel;
- LocalServerChannel;
- NioDatagramChannel;
- NioSctpChannel;
- NioSocketChannel。
EventLoop 接口
EventLoop 定义了 Netty 的核心抽象,用于处理连接的生命周期中所发生的事件。
一个 EventLoopGroup 包含一个或者多个 EventLoop;
- 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
- 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
- 一个 Channel 在它的生命周期内只注册于一个 EventLoop;
- 一个 EventLoop 可能会被分配给一个或多个 Channel。
ChannelFuture 接口
Netty 提供了ChannelFuture 接口,其 addListener()方法注册了一个 ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。
ChannelHandler 接口
ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。
ChannelPipeline 接口
ChannelHandler 安装到 ChannelPipeline 中的过程如下所示:
- 一个ChannelInitializer的实现被注册到了ServerBootstrap中;
- 当 ChannelInitializer.initChannel()方法被调用时,ChannelInitializer 将在 ChannelPipeline 中安装一组自定义的 ChannelHandler;
- ChannelInitializer 将它自己从 ChannelPipeline 中移除。
编码器和解码器
- 当你通过 Netty 发送或者接收一个消息的时候,就将会发生一次数据转换。入站消息会被解码;也就是说,从字节转换为另一种格式,通常是一个 Java 对象。如果是出站消息,则会发生相反方向的转换:它将从它的当前格式被编码为字节。这两种方向的转换的原因很简单:网络数据总是一系列的字节。
第4章 传输
- NIO——非阻塞 I/O
- 零拷贝:
零拷贝(zero-copy)是一种目前只有在使用 NIO 和 Epoll 传输时才可使用的特性。它使你可以快速高效地将数据从文件系统移动到网络接口,而不需要将其从内核空间复制到用户空间,其在像FTP或者HTTP 这样的协议中可以显著地提升性能。
第6章
ChannelHandler。
- Channel 的状态模型:ChannelUnregistered --> ChannelRegistered --> ChannelActive --> ChannelInactive
当这些状态发生改变时,将会生成对应的事件。
这些事件将会被转发给 ChannelPipeline 中的 ChannelHandler,其可以随后对它们做出响应。 - Netty 定义了下面两个重要的 ChannelHandler 子接口:
ChannelInboundHandler——处理入站数据以及各种状态变化;
ChannelOutboundHandler——处理出站数据并且允许拦截所有的操作。
第七章
EventLoop和线程模型
线程模型概述
基本的线程池化模式可以描述为:
- 从池的空闲线程列表中选择一个Thread,并且指派它去运行一个已提交的任务(一个Runnable的实现);
- 当任务完成时,将该Thread返回给该列表,使其可被重用。
EventLoop接口
- 运行任务来处理在连接的生命周期内发生的事件是任何网络框架的基本功能。与之相应的编程上的构造通常被称为事件循环—一个Netty使用了interface io.netty.channel.EventLoop来适配的术语。
- EventLoop采用了两个基本的API:并发和网络编程.
Netty4中的I/O和事件处理
- 由I/O操作触发的事件将流经安装了一个或者多个ChannelHandler的ChannelPipeline。传播这些事件的方法调用可以随后被ChannelHandler所拦截,并且可以按需地处理事件。
使用EventLoop调度任务
-
Netty通过Channel的EventLoop实现任务调度
-
Netty的EventLoop扩展了ScheduledExecutorService ,所以它提供了使用JDK实现可用的所有方法,包括 schedule()和scheduleAtFixedRate()方法。
-
要想取消或者检查(被调度任务的)执行状态,可以使用每个异步操作所返回的ScheduledFuture。
-
线程管理。确定它是否是分配给当前Channel以及它的EventLoop的那一个线程。
EventLoop将负责处理一个Channel的整个生命周期内的所有事件。 -
每个EventLoop都有它自已的任务队列。
* 永远不要将一个长时间运行的任务放入到执行队列中,因为它将阻塞需要在同一线程上执行的任何其他任务。”
如果必须要进行阻塞调用或者执行长时间运行的任务,建议使用一个专门的 EventExecutor 。
EventLoop/线程的分配
服务于 Channel 的 I/O 和事件的 EventLoop 包含在 EventLoopGroup 中。
EventLoopGroup 负责为每个新创建的 Channel 分配一个 EventLoop。
一旦一个 Channel 被分配给一个 EventLoop,它将在它的整个生命周期中都使用这个EventLoop。
一个EventLoop 通常会被用于支撑多个 Channel,所以对于所有相关联的 Channel 来说,ThreadLocal 都将是一样的。
- 异步传输:
- 阻塞传输: