点击查看 《Netty in Action》笔记目录。
本文是对《Netty in Action》第3章内容的笔记和翻译,主要内容包括:
- Netty 技术和架构方面的介绍
-
Channel、EventLoop和ChannelFuture -
ChannelHandler和ChannelPipeline - 启动( Bootstrapping)
Channel、EventLoop 和 ChannelFuture
下面这些可以被看做 Netty 网络模型的抽象:
-
Channel:Sockets -
EventLoop:流控、多线程、并发 -
ChannelFuture:异步通知
Channel 接口
Java 中基本的网络 I/O 操作依赖 Socket 类,如:bind()、connect()、read() 和 write()。Netty 中的 Channel 接口提供的 API 与直接操作 Sockets API 相比,大大减少了复杂度。此外,Channel 是很多专有实现的基类,这些实现包括:
EmbeddedChannelLocalServerChannelNioDatagramChannelNioSctpChannelNioSocketChannel
EventLoop 接口
EventLoop 定义了 Netty 核心抽象:在一个连接周期中处理发生的事件。图3.1 在一个高层次展示了 Channels、EventLoops、Threads 和 EventLoopGroups 的关系。关系如下:
- 一个
EventLoopGroup包括了一个或多个EventLoop。 - 一个
EventLoop在生命周期中只会绑定到一个单一的线程Thread。 - 所有被
EventLoop处理的 I/O 事件都会被它绑定的线程Thread处理。 - 一个
Channel会在一个单一的EventLoop中注册它的生命周期。 - 一个单一的
EventLoop可能会被分配多个Channels。

值得注意的是:在这个设计中,一个给定 Channel 的 I/O 的操作都是由同一个线程执行的,也就是这些操作是单线程的,所以无形中消除了同步的问题。
ChannelFuture 接口
Netty 中提供了 ChannelFuture,这个接口具有 addListener()方法,通过这个方法可以注册一个 ChannelFutureListener,这个监听器会在方法结束时被调用(无论执行是否成功)。
ChannelFuture 的更多介绍
可以把ChannelFuture看做是存放结果的一个站位空间,这里的结果将会在未来的某个时刻被填满。可以确定的是:对于同一个Channel的所有操作都会被执行,并且执行顺序与调用的顺序一致。
ChannelHandler 和 ChannelPipeline
ChannelHandler 接口
对于应用开发者来说,Netty 中最重要的组件是 ChannelHandler。这个组件承载了对输入输出数据的所有应用处理逻辑。
事实上,ChannelHandler 几乎可以满足所有的操作,包括:转换数据的格式、处理过程中抛出的异常等。
ChannelPipeline 接口
ChannelPipeline 提供了链式 ChannelHandlers 的一个承载容器,并且定义了在这个链中传递输入输出事件的 API。当一个 Channel 被创建的时候,它会被自动绑定到它所在的 ChannelPipeline 上。
ChannelHandler 通过以下步骤安装到 ChannelPipeline 上:
- 在
ServerBootstrap上注册一个ChannelInitializer的实现。 - 当
ChannelInitializer.initChannel()函数被调用的时候,ChannelInitializer将会在 pipeline 中安装一系列的ChannelHandler。 - 然后
ChannelInitializer从ChannelPipeline中把它自身删除。
图3.2 展示了从 ChannelHandler 派生出来的 ChannelInboundHandler 和 ChannelOutboundHandler。

图3.3 展示了 Netty 应用中输入和输出端数据流的区别。

输入输出处理器的更多介绍
一个事件可以通过ChannelHandlerContext传递到链中的下一个处理器,ChannelHandlerContext在每个方法中都会被作为一个输入参数。因为你有时候会忽略不感兴趣的事件,Netty 提供了抽象基础类ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter。每个都提供了方法实现,通过调用ChannelHandlerContext中的相应方法,可以简化传递到下一个处理者的操作。然后,你可以扩展类并覆盖你感兴趣的方法。
当两种类别处理器在同一个 ChannelPipeline 中混合时会发生什么?尽管输入输出 handler 都继承于 ChannelHandler,但 Netty 还是会区分 ChannelInboundHandler 和 ChannelOutboundHandler 的不同实现,并且 确保数据只在方向相同类型的 handler 之间传递。
当 ChannelHandler 被加入到 ChannelPipeline 中时,它会被分配一个 ChannelHandlerContext,这个 ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之间的绑定关系。
Netty 中发送数据的方法有两种:
- 直接写入到
Channel,消息会从ChannelPipeline的尾部开始传递。 - 写入到
ChannelHandler关联的ChannelHandlerContext,消息会从ChannelPipeline中的__下一个__handler 开始传递。
进一步了解 ChannelHandler
Netty 中有一些适配类,从而减少了常用 ChannelHandler 类的工作量。这些适配类提供了相应接口方法的默认实现。
你在写 handle 时经常会用到的适配类包括:
ChannelHandlerAdapterChannelInboundHandlerAdapterChannelOutboundHandlerAdapterChannelDuplexHandlerAdapter
编码和解码
当你使用 Netty 发送和接收数据时,需要进行数据格式的转换。输入的消息将会被解码,也就是将输入的字节转换为另一种格式,典型的是转化为一个 Java 对象。如果数据是输出的,相反的操作将会发生:会从现在的格式编码成自己。需要这样做的原因很简单,网络传输的数据是一系列的字节。
所有 Netty 提供的编码解码(encoder/decoder)的适配类都实现于 ChannelInboundHandler 或者 ChannelOutboundHandler。
你将发现:对于输入数据,channelRead 方法或事件会被覆写。对于输入 Channel 的每条数据,这个方法都会被调用。然后会调用解码器的 decode() 方法,并将解码后的字节传输到 pipeline 中的下一个 ChannelInboundHandler。
而输出数据的模式正好相反:编码器将消息转换为字节,并将这些字节传递到下一个 ChannelOutboundHandler。
抽象类 SimpleChannelInboundHandler
很常见的是,你的应用需要实现一个 handler 来接受解码的数据并且在数据上实现应用逻辑。创建这样的一个 ChannelHandler,你只需要扩展基类 SimpleChannelInboundHandler<T>,其中 T 是你需要处理的数据对应的 Java 类型。
这个 handler 中最重要的一个方法是 channelRead0(ChannelHandlerContext,T)。
Bootstrapping
Netty 的启动类可以为应用的网络层提供配置容器。
面向连接的协议
请记住:严格意义上的连接(“connection”)只适用于面向连接的协议,如 TCP。在这样的协议中,可以保证连接的端之间可以按序接收到数据。
相应的,有两种类型的启动类:一个是客户端(Bootstrap),另一个是服务端(ServerBootstrap)。
下表对这两种类型的启动进行了比较。
| 类别 | Bootstrap | ServerBootstrap |
|---|---|---|
| 网络功能 | 连接远程主机和端口 | 绑定本地端口 |
EventLoopGroup 的数目 |
1 | 2 |
启动一个客户端只需要一个 EventLoopGroup,但是服务端的 ServerBootstrap 需要两个(可以是同一个实例),这是为什么?
服务端需要两个区分的 Channel 集合。第一个集合包括一个 ServerChannel,它代表了服务端自己用于监听(listening)的 socket,绑定到本地端口;第二个集合包括了所有创建的 Channels,用于处理输入服务端接收到的各个客户端连接。图3.4 展示了这个模型,并说明了为什么需要2个不同的 EventLoopGroups。

与 ServerChannel 相关的 EventLoopGroup 为 ServerChannel分配了一个 EventLoop,这个 EventLoop 负责为即将到来的连接请求创建 Channel。一旦接收到连接,第二个 EventLoopGroup 会为创建出来的 Channel 分配对应的 EventLoop。