Netty 的组件和设计

关注微信公众号(瓠悠笑软件部落),大家一起学习,一起摸鱼。
huyouxiao.com
本章讲介绍:

  • Netty的技术和架构方面
  • Channel , EventLoop , 和 ChannelFuture
  • ChannelHandler 和 ChannelPipeline
  • Bootstrapping

在第1章中,我们总结了历史和技术基础 Java 中的高性能网络编程。 这提供了背景
概述Netty的核心概念和构建块。

在第2章中,我们将讨论范围扩展到应用程序开发。 通过构建一个简单的客户端和服务器,您了解了自举和通过最重要的ChannelHandler API获得实践经验。 沿着方式,您还验证了您的开发工具是否正常运行。

当我们在本书的其余部分中使用这些材料时,我们将从两个方面探索 Netty 不同但密切相关的观点:作为一个类库和一个框架。 都对于使用Netty编写高效,可重用和可维护的代码至关重要。

从高层次的角度来看,Netty解决了两个相关的关注领域,我们可能将其广泛地称为技术和架构。 首先,它基于Java NIO构建的异步和事件驱动实现可确保在高负载下实现最高的应用程序性能和可伸缩性。 其次,Netty体现了一个将应用程序逻辑与网络层分离的一组设计模式,简化了在最大限度地提高代码的可测试性,模块性和可重用性的同时实现开发。
在我们更详细地研究Netty的各个组件时,我们将密切关注他们如何协作以支持这些架构最佳实践。 依照同样的原则指示,我们可以获得 Netty 可以提供的所有好处。 有了这个目标,请注意,在本章中,到现在为止,我们将回顾我们介绍的主要概念和组件。

3.1 Channel, EventLoop 和 ChannelFuture

以下部分将详细介绍我们对Channel,EventLoop的讨论,和ChannelFuture类一起被认为是代表的
Netty的网络抽象:

  • Channel —— Sockets
  • EventLoop —— 控制流,多线程,并发
  • ChannelFuture —— 异步通知 (Asynchronous notification)
3.1.1 Interface Channel

Channel methods
基本I / O操作(bind(),connect(),read() 和write() )取决于基元由底层网络传输提供。 在基于Java的网络中,基本结构是类Socket。 Netty 的 Channel 接口提供了极大的API
降低了直接使用 Socket 的复杂性。 此外,频道是具有许多预定义的专用实现的扩展类层次结构的根,其中以下是一个简短列表:

  • EmbeddedChannel
  • LocalServerChannel
  • NioDatagramChannel
  • NioSctpChannel
  • NipSocketChannel

Interface Channel

3.1.2 Interface EventLoop

EventLoop 定义了在连接建立的生命周期中, Netty 用于处理期间发生的事件的核心抽象。 我们将在第7章的内容中详细讨论 EventLoop 。Netty 的线程处理模型的文本。 目前,下图说明了高水平 Channel,EventLoop,Thread 和 EventLoopGroup 之间的关系。
Channels, EventLoops, and EventLoopGroups
这些关系是:

  • 一个 EventLoopGroup 包含一个或者多个 EventLoops.
  • EventLoop在其生命周期内绑定到单个Thread。
  • EventLoop处理的所有 I/O events 都在其专用线程上处理。
  • 一个 Channel 使用单个 EventLoop 进行生命周期注册。
  • 可以将单个 EventLoop 分配给一个或多个 Channel 。

3.1.3 Interface ChannelFuture

正如我们已经解释过的,Netty 中的所有I / O操作都是异步的。 因为操作可能不会立即返回,所以我们需要一种方法来在以后确定其结果。为此,Netty 提供了 ChannelFuture,其addListener() 方法注册了一个 ChannelFutureListener,以便在操作完成时通知(无论是否成功))。

More on ChannelFuture
将 ChannelFuture 视为将来结果要执行的操作的占位符,。 什么时候会执行可能取决于几个因素,因此无法预测精确,但可以肯定它会被执行。 此外,所有属于同一频道的操作都保证在调用它们的顺序。

我们将在第 7 张深入讨论 EventLoop 和 EventLoopGroup.

3.2 ChannelHandler and ChannelPipeline

现在我们将更详细地了解管理数据流的组件并执行应用程序的处理逻辑。

3.2.1 Interface ChannelHandler

从应用程序开发人员的角度来看,Netty 的主要组件是 ChannelHandler,用作应用于处理入站(inbound)和出站(outbound)数据的所有应用程序逻辑的容器。 这是可能的,因为 ChannelHandler 方法由网络事件触发(其中术语“事件”被广泛使用)。 事实上,ChannelHandler 几乎可以用于任何类型的操作,例如将数据从一种格式转换为另一种格式或处理处理期间抛出的异常。

例如,ChannelInboundHandler 是您经常实现的子接口。 此类型接收由应用程序的业务逻辑处理的入站事件和数据。 您还可以在从时刷新 ChannelInboundHandler 中的数据,您正在向已连接的客户发送回复。 应用程序的业务逻辑通常驻留在一个,或多个ChannelInboundHandler 中。

3.2.2 Interface ChannelPipeline

ChannelPipeline 为 ChannelHandler 链提供容器,并定义用于在链中传播入站(inbound)和出站(outbound)事件流的API。创建 Channel 时,会自动为其分配自己 ChannelPipeline。

ChannelHandler 安装在 ChannelPipeline 中,如下所示:

  • ChannelInitializer 的实现类在 ServerBootstrap 中注册。
  • 当调用 ChannelInitializer.initChannel() 时, ChannelInitializer 在 pipeline 中安装了一组定制化的 ChannelHanders。
  • ChannelInitializer 从 ChannelPipeline 中把自己移除。
    让我们更深入地了解 ChannelPipeline 和 ChannelHandler 之间的共生关系,以检查发送或接收数据时数据会发生什么。

ChannelHandler专为支持广泛的用途而设计,您可以将其视为处理进出 ChannelPipeline的事件(包括数据)的任何代码的通用容器。 如下图所示,它显示了ChannelHandler的ChannelInboundHandler和ChannelOutboundHandler的派生。

事件在管道的移动,是ChannelHandler在应用程序的初始化,或引导阶段安装时的任务。 这些对象接收事件,执行已实现的处理逻辑,并将数据传递给链中的下一个处理程序。 它们的执行顺序取决于它们的添加顺序。 出于所有实际目的,我们将C hannelHandler 的这种有序排列称为 ChannelPipeline 。

ChannelHandler class hierarchy

ChannelPipeline 的 inbound ChannelHandlers 和 outbound ChannelHandlers

ChannelPipeline with inboundand outbound ChannelHandlers

上图说明了 Netty 应用程序中入站(inbound)和出站(outbound)数据流之间的区别。 从客户端应用程序的角度来看,如果移动是从客户端到服务器,则事件被称为出站(outbound),而在相反的情况下则是入站(inbound)。

上图还显示入站(inbound)和出站(outbound)处理程序都可以安装在同一个管道中。 如果读取了消息或任何其他入站事件,它将从管道的头部开始并传递给第一个ChannelInboundHandler。 此处理程序可能会或可能不会实际修改数据,具体取决于其具体功能将数据传递给链中的下一个ChannelInboundHandler。 最后,数据将到达管道(pipeline)的尾部,此时所有处理都将终止。

数据的出站移动(即,正在写入的数据)在概念上是相同的。 在这种情况下,数据从尾部流经ChannelOutboundHandler链直到它到达头部。 除此之外,出站数据将达到网络传输,此处显示为Socket。 通常,这将触发写入操作。

More on inbound and outbound handlers
通过使用作为每个方法的参数提供的 ChannelHandlerContext,可以将事件转发到当前链中的下一个处理程序。 因为您有时会忽略不感兴趣的事件,所以 Netty 提供了抽象基类ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter。 每个都提供了方法实现,只需通过调用 ChannelHandlerContext 上的相应方法将事件传递给下一个处理程序。 然后,您可以通过覆盖您感兴趣的方法来扩展该类。

鉴于出站和入站操作是不同的,您可能想知道当两个类别的处理程序在同一个ChannelPipeline中混合时会发生什么。虽然入站(inbound)和出站(outbound)处理程序都扩展了ChannelHandler,但Netty区分了ChannelInboundHandler和ChannelOutboundHandler的实现。并确保数据仅在相同方向类型的处理程序之间传递。

在Netty中有两种发送消息的方法。 您可以直接写入 Channel 或写入与 ChannelHandler 关联的 ChannelHandlerContext 对象。 前一种方法导致消息从ChannelPipeline的尾部开始,后者导致消息从 ChannelPipeline 中的下一个处理程序开始。

3.2.3 A closer look at ChannelHandlers

正如我们之前所说,ChannelHandler 有许多不同类型,每个类型的功能很大程度上取决于它的超类。 Netty 提供了许多适配器类形式的默认处理程序实现,旨在简化应用程序处理逻辑的开发实现。你已经看到了在链中每个管道中的 ChannelHandler 负责将事件转发到下一个处理程序。这些适配器类(及其子类)会自动执行此操作,因此您可以这样做只能覆盖您想要专门化的方法和事件。

Why adapters?
有一些适配器类可以减少将自定义ChannelHandler编写到最低限度的工作量,因为它们提供了接口中定义的所有方法相应的默认实现。
这些是您在创建自定义处理程序时最常调用的适配器:

  • ChannelHandlerAdapter
  • ChannelInboundHandlerAdapter
  • ChannelOutboundHandlerAdapter
  • ChannelDuplexHandlerAdapter

接着我们会阐 ChannelHandler 的三种子类型: encoders, decoders, 和 SimpleChannelInboundHandler, SimpleChannelInboundHandler 是 ChannelInboundHandlerAdapter 的子类。

3.2.4 encoders and decoders

当您使用Netty发送或接收消息时,将进行数据转换。一个入站消息将被解码;也就是说,从字节转换为另一种格式,通常是Java对象。如果消息是出站的,则会发生相反的情况:它将是从当前格式编码为字节。转换的原因很简单:网络数据始终是一系列字节。

为编码器和解码器提供各种类型的抽象类,以满足特定需求。例如,您的应用程序可能使用不需要立即将消息转换为字节的中间格式。你仍然需要一个编码器,但它将来自不同的超类。确定适当的一个,你可以应用一个简单的命名约定。

通常,基类的名称类似于 ByteToMessageDecoder 或 MessageToByteEncoder。在特殊类型的情况下,您可能会发现一些东西,像 ProtobufEncoder 和 ProtobufDecoder,提供支持谷歌的 protocol buffers。

严格来说,其他处理程序可以做编码器和解码器所做的事情。只是
因为有适配器类来简化通道处理程序的创建,所有的 Netty 提供的编码器/解码器适配器类(encoder/decoder adapter) 实现。是 ChannelInboundHandler 和 ChannelOutboundHandler 的二选一。

您会发现,对于入站数据(inbound data),将覆盖channelRead方法/事件。对于从入站通道(inbound Channel)读取的每条消息,都会调用此方法。然后, 它会调用所提供的解码器的 decode() 方法并转发解码的字节(decoded bytes)到管道中的下一个ChannelInboundHandler。

3.2.5 Abstract class SimpleChannelInboundHandler

最常见的情况是,您的应用程序将使用处理程序来接收已解码的消息并将业务逻辑应用于数据。要创建这样的ChannelHandler,你只需要扩展基类SimpleChannelInboundHandler ,其中T是要处理的消息的Java类型。在这个处理程序中,你将覆盖一个或更多基类的方法并获取对ChannelHandlerContext的引用,它作为输入参数传递给所有处理程序方法。
这种类型的处理程序中最重要的方法是 channelRead0(ChannelHandlerContext,T)。除了要求不阻止当前I / O线程外,实现完全取决于您。

3.3 Bootstrapping

Netty的引导类为应用程序的配置提供网络层容器,涉及将进程绑定到给定端口或连接
在指定端口的指定主机上运行的另一个进程。通常,我们将前一个用例称为引导服务器, 后者作为引导客户端。这个术语简单方便,但略有不同。它模糊了“服务器”和“客户端”这两个术语表示不同网络工作行为的重要事实;即,服务器监听传入连接。客户端与远端服务器的一个或多个进程建立连接。

connection-oriented protocols 请记住,严格来说,术语“连接”仅适用于面向连接的协议, 例如TCP,它保证了消息在连接端点之间有序的传递。

因此,有两种类型的引导:一种用于客户端(简称为Bootstrap),另一种用于服务器(ServerBootstrap)。无论您的应用程序使用哪种协议或它执行的数据处理类型,唯一确定它使用的引导类的是它作为客户端或服务器的功能。表3.1比较了两种类型的bootstraps。

Comparison of Bootstrap classes

已经讨论了两种类型的bootstrap之间的第一个区别:ServerBootstrap 绑定到端口(port),因为服务器必须侦听连接,而 Bootstrap 由想要连接到远程服务器(需要远端服务器IP地址和端口)的对等的客户端应用程序使用。

第二个区别可能更重要。引导客户端需要只有一个 EventLoopGroup,但 ServerBootstrap需要两个(可以是同一个例子)。为什么?

服务器需要两组不同的Channel。第一个集合将包含一个ServerChannel,表示服务器自己的侦听套接字,绑定到本地端口。第二组将包含为处理传入客户端连接而创建的所 Channel - 一个用于服务器已接受的每个连接。图3.4说明了这个模型,并说明了为什么需要两个不同的 EventLoopGroup。

Server with two EventLoopGroups

与ServerChannel关联的EventLoopGroup分配EventLoop, 负责为传入的连接请求创建Channel。一旦接受连接,第二个EventLoopGroup就会分配一个EventLoop 给
它的 Channel。

3.4 Summary

在本章中,我们从技术和架构的角度讨论了理解Netty的重要性。我们更详细地回顾了一些概念以前介绍过的组件,特别是ChannelHandler,ChannelPipeline,和 bootstrapping。

特别是,我们讨论了ChannelHandler的层次结构并进行了介绍编码器和解码器,描述它们在转换数据时的互补功能来自网络字节格式。以下章节中的许多章节都致力于深入研究这些组件,这里提供的概述可以帮助您保持关注的重点。下一章将探讨Netty提供的网络传输以及如何使用选择最适合您应用的一种。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值