Netty的组件和设计
概述
Netty解决了两个开发的关注领域,我们可将其大致标记为技术和体系结构。它是基于Java NIO的异步和事件驱动的实现,以及大部分的设计模式,将应用程序逻辑从网络层解耦。
先大致认为Netty网络的抽象如下:
Channel——Socket
EventLoop——控制流、多线程处理、并发
ChannleFuture——异步通知
Channle接口
基础的IO操作(bind,connect,read,write)依赖于底层网络传输所提供的原语,在基于Java的网络编程中,基本的构造是class Socket。Netty的channel接口所提供的API,Channel的内容有以下的
EventLoop接口
EventLoop定义了Netty的核心抽象,用于处理连接的生命周期中所发生的事件。结合Netty的线程处理模型的上下文对EventLoop进行详细的讨论。Channel、EventLoop、EventLoopGroup之间的关系:
一个EventLoopGroup包含一个或者多个EventLoop
一个EventLoop在它的生命周期内只和一个Thread绑定
所有由EventLoop处理的I/O事件都将在它专有的Thread被处理
一个Channel在它的生命周期里面只注册一个EventLoop
一个EventLoop只能被分配给一个或者多个Channel
ChannelFuture接口
Netty 中所有的 I/O 操作都是异步的。因为一个操作可能不会 立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。可以将 ChannelFuture 看作是将来要执行的操作的结果的 占位符。它究竟什么时候被执行则可能取决于若干的因素,因此不可能准确地预测,但是可以肯 定的是它将会被执行。此外,所有属于同一个 Channel 的操作都被保证其将以它们被调用的顺序 被执行。
ChannelHandler接口
Netty 的主要组件是 ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。ChannelHandler的方法是 由网络事件(其中术语“事件”的使用非常广泛)触发的。事实上,ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另外一种格式,或者处理转换过程中所抛出的异常。
ChannelInboundHandler是一个你将会经常实现的子接口。这种类型的ChannelHandler 接收入站事件和数据,这些数据随后将会被你的应用程序的业务逻辑所处 理。当你要给连接的客户端发送响应时,也可以从ChannelInboundHandler冲刷数据。你的应用程序的业务逻辑通常驻留在一个或者多个ChannelInboundHandler中。
ChannelPipeline接口
ChannelPipeline 提供了 ChannelHandler链的容器,并定义了用于在该链上传播入站和出站事件流的 API。当Channel被创建时,它会被自动地分配到它专属的ChannelPipeline。
ChannelHandler 安装到 ChannelPipeline 中的过程如下所示:
1.一个ChannelInitializer的实现被注册到了ServerBootstrap中
2.当 ChannelInitializer.initChannel()方法被调用时,ChannelInitializer 将在 ChannelPipeline 中安装一组自定义的 ChannelHandler
3.ChannelInitializer 将它自己从 ChannelPipeline 中移除。————>最后依然是一个ChannelHandler
ChannelPipeline仅仅是一个限定着ChannelHandler的容器
如果一个消息或者任何其他的入站事件被读取,那么它会从 ChannelPipeline 的头部 开始流动,并被传递给第一个 ChannelInboundHandler。这个 ChannelHandler 不一定 会实际地修改数据,具体取决于它的具体功能,在这之后,数据将会被传递给链中的下一个 ChannelInboundHandler。最终,数据将会到达 ChannelPipeline 的尾端,届时,所有 处理就都结束了。 数据的出站运动(即正在被写的数据)在概念上也是一样的。在这种情况下,数据将从 ChannelOutboundHandler 链的尾端开始流动,直到它到达链的头部为止。在这之后,出站 数据将会到达网络传输层,这里显示为 Socket。通常情况下,这将触发一个写操作。
道如果将两个类别的 ChannelHandler 都混合添加到同一个 ChannelPipeline 中会发生什么。Netty 能区分 ChannelInboundHandler 实现和 ChannelOutboundHandler 实现,并确保数据只会在具有相同定 向类型的两个 ChannelHandler 之间传递——————>传递一个异常?————>并不会
当ChannelHandler 被添加到ChannelPipeline 时,它将会被分配一个ChannelHandlerContext,其代表了 ChannelHandler 和 ChannelPipeline 之间的绑定。虽然这个对象可 以被用于获取底层的 Channel,但是它主要还是被用于写出站数据。
ChannelHandler
有许多不同类型的 ChannelHandler,它们各自的功能主要取决于 它们的超类。Netty 以适配器类的形式提供了大量默认的 ChannelHandler 实现,其旨在简化 应用程序处理逻辑的开发过程。你已经看到了,ChannelPipeline中的每个ChannelHandler 将负责把事件转发到链中的下一个 ChannelHandler。这些适配器类(及它们的子类)将自动 执行这个操作,所以你可以只重写那些你想要特殊处理的方法和事件。
编码器、解码器都是属于ChannelHandler
编码器
当你通过 Netty 发送或者接收一个消息的时候,就将会发生一次数据转换。入站消息会被解 码;也就是说,从字节转换为另一种格式,通常是一个 Java 对象。如果是出站消息,则会发生 相反方向的转换:它将从它的当前格式被编码为字节。这两种方向的转换的原因很简单:网络数 据总是一系列的字节。 对应于特定的需要,Netty 为编码器和解码器提供了不同类型的抽象类。例如,你的应用程 序可能使用了一种中间格式,而不需要立即将消息转换成字节。你将仍然需要一个编码器,但是 它将派生自一个不同的超类。为了确定合适的编码器类型,你可以应用一个简单的命名约定。
所有由 Netty 提供的编码器/解码器适配器类都实现 了 ChannelOutboundHandler 或者 ChannelInboundHandler 接口。
最常见的情况是,你的应用程序会利用一个 ChannelHandler 来接收解码消息,并对该数据应用业务逻辑。要创建一个这样的 ChannelHandler,你只需要扩展基类 SimpleChannelInboundHandler,其中 T 是你要处理的消息的 Java 类型 。在这个 ChannelHandler 中, 你将需要重写基类的一个或者多个方法,并且获取一个到 ChannelHandlerContext 的引用, 这个引用将作为输入参数传递给 ChannelHandler 的所有方法。
为什么ServerBootStrap需要两个EventLoopGroup(虽然可以是同一个)——————>Server,BootStarp仅仅需要一个EventLoopGroup呢——————>Client?
因为服务器需要两组不同的 Channel。第一组将只包含一个 ServerChannel,代表服务 器自身的已绑定到某个本地端口的正在监听的套接字。而第二组将包含所有已创建的用来处理传 入客户端连接(对于每个服务器已经接受的连接都有一个)的 Channel。