《Netty in action》 第三章 Netty 组件和设计

第三章 Netty 组件和设计

从高层视角,Netty address 两个等价的关注域:技术和架构
* 首先,它是构建于 Java NIO 上的异步、事件驱动的实现,保证了在高负载下最大的应用性能和伸缩性;
* Netty 体现了一组设计模式,从网络层解耦应用逻辑,简化开发,最大化可测试性、模块化、代码重用。

当我们更细微地学习 Netty 的独立组件时,我们将聚焦于它们是如何协作来支持这些架构最佳实践。通过遵循同样的原则,我们将获得 Netty 能提供的所有好处。

3.1 Channel, EventLoop, ChannelFuture

Channel, EventLoop, ChannelFuture 放到一起,可以代表了 Netty 的网络层抽象:

  • Channel: Sockets
  • EventLoop: 控制流,多线程,并发
  • ChannelFuture: 异步通知

3.1.1 Channel 接口

基本的 I/O 操作(bind(), connect(), read(), write()) 依赖于底层网络传输的提供的原子操作。在基于 Java 的网络里,基础结构是 Socket 类。Netty 的 Channel 接口提供的 API 极大简化了直接操作 Sockets 的复杂工作。

3.1.2 EventLoop 接口

EventLoop 定义了 Netty 处理一个连接生命周期里发生的事件的核心抽象。Channel、EventLoop、EventLoopGroup 之间的关系如下:

  • 一个 EventLoopGroup 包含一个或多个 EventLoop;
  • 一个 EventLoop 在它的生命周期里是绑定到单一线程的;
  • 一个 EventLoop 处理的所有 I/O 事件都是在它的专用线程上进行的;
  • 一个 Channel 在它的生命周期里是注册到到单一的 EventLoop;
  • 单一的 EventLoop 可能被赋给一个或多个 Channel。

注意,在这种设计里,给定 Channel 的 I/O 事件都由同一个线程来执行,事实上消除了对同步的需要。

3.1.3 ChannelFuture 接口

Netty 里的所有 I/O 操作都是异步的。因为一个操作可能没法立即返回,我们需要一种方法来稍后确定它的结果。处于这种目的,Netty 提供了 ChannelFuture ,它的 addListener() 方法可以注册 ChannelFutureListener 来在操作完成(不管是否成功)时获得通知。

可以把 ChannelFuture 看作是一个在未来执行的操作的结果占位符。它什么时候执行取决于多个因素,因此没法精确预测,但它肯定会被执行。更进一步,属于同一个 Channel 的所有操作保证按它们发起的顺序执行。

3.2 ChannelHandler 和 ChannelPipeline

这两个组件用于管理数据流动和执行应用程序的处理逻辑。

3.2.1 ChannelHanlder 接口

从应用开发者的角度,Netty 的首要组件是 ChannelHandler,它作为所有用于处理入站、出站数据的应用程序逻辑的容器。事实上,ChannelHandler 可用于处理几乎所有的操作,例如进行数据格式转换、处理异常。

例如,ChannelInboundHandler 接收入站事件和被应用程序业务逻辑处理的数据。当你要发送响应给客户端连接时,你也可以在 ChannelInboundHandler 上 flush 数据。你的应用的业务逻辑通常存在于一个或多个 ChannelInboundHandler

3.2.2 ChannelPipeline 接口

ChannelPipeline 提供了一个 ChannelHandler 链的容器,定义了一组 API 用于沿着这个链传递入站和出站事件。当一个 Channel 被创建后,它自动被赋予它自己的 ChannelPipeline 。

ChannelHandler 按如下方式安装到 ChannelPipeline :

  • 一个 ChannelInitializer 实现被注册到 ServerBootstrap 。
  • ChannelInitializer.initChannel() 被调用,ChannelInitializer 安装一组 ChannelHandler 到 pipeline。
  • ChannelInitializer 从 ChannelPipeline 移除它自身。

ChannelHandler 特意设计为支持广泛的使用,你可以认为它是处理 进入、离开 ChannelPipeline 的事件(包括数据)的任意代码的容器。

在 pipeline 中移动事件是 ChannelHandlers 的工作。这些对象接收事件、执行它们实现的处理逻辑,传递数据给链条的下一个 handler 。它们执行的顺序取决于它们被添加的顺序。

图 3.3 显示了入站和出站处理器被安装到同一个 pipeline。如果一个消息或任意凄然入站事件被读取,它将从 pipeline 的头部开始,传递给第一个 ChannelInboundHandler 。这个处理器可能、也可能不实际修改数据,取决于它的具体功能,然后数据被传递给链条里的下一个 ChannelInboundHandler 。最终,数据将到达 pipeline 的尾部,在这里,所有的处理都结束了。

数据的出站运动在概念上是完全一样的。在这种情况下,数据从 ChannelOutboundHandler 链的尾部流过链条直到头部。超出这个点后,出站数据将到达网络传输,这里的是 Socket。典型地,这将触发一个写操作。

一个事件将向前转发给当前链条里的下一个 handler,通过使用 ChannelHandlerContext 对象。因为有时候需要忽略一些不敢兴趣的事件,Netty 提供了抽象基类 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 。每个类提供的方法都简单地用 ChannelHandlerContext 对象简单地把事件传递给链条中的下一个 handler。

给定的出站和入站操作是不同的。虽然入站和出站的 handler 都扩展自 ChannelHandler,Netty 区分 ChannelInboundHandler 和 ChannelOutboundHandler 的实现,保证数据只在同一方向的 handler 类型之间传递。

当 ChannelHandler 被添加到 ChannelPipeline ,它被赋予一个 ChannelHandlerContext,表示 ChannelHandler 和 ChannelPipeline 之间的绑定。虽然这个对象可用于获取底层的 Channel,它通常用于写出站数据。

在 Netty 发送消息的两种方式:
* 直接写入 Channel :从 ChannelPipeline 的尾部开始(也就是所有 Handler 都有机会进行处理);
* 写入 ChannelHandlerContext :消息从 ChannelPipeline 下一个处理器开始;

3.2.3 近看 ChannelHandler

有很多种类型的 ChannelHandler,每个的功能大多由它的父类决定。Netty 以适配器类的形式提供了很多默认处理器的实现,用于简化应用程序处理逻辑的开发。pipeline 里的每个 ChannelHandler 负责把事件正向传递到链里的下一个处理器。适配器类和它的子类自动完成这些,你只需要覆写希望定制化传递方法和事件。

最常用的的适配器:

  • ChannelHandlerAdapter
  • ChannelInboundHandlerAdapter
  • ChannelOutboundHandlerAdapter
  • ChannelDuplexHandlerAdapter

3.2.4 Encoder 和 Decoder

当你用 Netty 发送或接收消息时,数据转换发生了。入站消息将被 解码:从字节转换到另一个格式,通常是 Java 对象。如果消息出站,将发生发转:消息将从当前格式编码为字节。这两种转换的理由很简单:网络数据总是一序列的字节。

各种不同的抽象类被提供用于编码和解码,对应于具体的需要。例如,应用程序可能用一种中间格式,不要求消息立即转换到字节。你仍然需要一个编码器,它将继承自不同的父类。

严格地讲,其他的处理器也可以做编码器和解码器的工作。但正如存在那些适配器用于简化 channel 处理器的创建,Netty 提供的所有编码器/解码器要么是 ChannelInboundHandler 要么是 ChannelOutboundHandler 的实现。

你将发现,对于入站数据, channelRead 方法/事件被覆写。对于从入站管道读取的每个消息,这个方法将被调用。然后调用解码器提供的 decode() 方法,然后把解码得到的字节传递给 pipeline 里的下一个 ChannelInboundHandler 。

对于出站消息的模式是相反的:一个编码器把消息转换为字节,正向传递给下一个 ChannelOutboundHandler 。

3.2.5 SimpleChannelInboundHandler 抽象类

通常,你的应用程序采用一个处理器来接收解码后的消息,对数据应用业务逻辑。为创建这样一个 ChannelHandler ,你只需要继承自基类 SimpleChannelInboundHandler<T>T 是你想处理的 Java 消息类型。

这种类型的处理器最重要的方法

3.3 Boostrapping

Netty 的 bootstrap 类提供了应用的网络层配置的容器,它要么绑定一个进程到给定端口,要么连接一个进程到另一个运行在指定主机和端口的进程。

“服务器”是监听一个或多个入站的进程,用 ServerBootstrap;“客户端”是建立连接,用 Bootstrap

面向连接的协议:“连接”这个术语只应用于面向连接的协议,例如 TCP,它保证连接两端传递的消息的顺序。

Category Bootstrap ServerBootstrap
网络功能 连接到一个远程主机和端口 绑定到本地端口
EventLoopGroup 数量 1 2

一个服务器需要区分两组 Channel。第一组包含单一的 ServerChannel,表示服务器自身正在监听的套接字,绑定到一个本地端口。第二组包含所有已经创建的用于处理到来的客户端连接的 Channel — 每个服务器已接受的连接对应一个 channel。

3.4 总结


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据