Netty实战(三)
一、Channel、EventLoop 和 ChannelFuture
上一篇博文我们在构建服务端和客户端中出现了一些新的类,可能有些同学还有些不了解它们的具体功能。没关系,接下来我们对于 Channel、EventLoop 和 ChannelFuture 类进行的讨论增添更多的细节,这些类合在一起,可以被认为是 Netty 网络抽象的代表:
- Channel : Socket;
- EventLoop : 控制流、多线程处理、并发;
- ChannelFuture : 异步通知。
1.1 Channel 接口
基本的 I/O 操作(bind()、connect()、read()和 write())依赖于底层网络传输所提供的原语。在基于 Java 的网络编程中,其基本的构造是 class Socket。Netty 的 Channel 接口所提供的 API,大大地降低了直接使用 Socket 类的复杂性。此外,Channel 也是拥有许多预定义的、专门化实现的广泛类层次结构的根,下面是一个简短的部分清单:
- EmbeddedChannel;
- LocalServerChannel;
- NioDatagramChannel;
- NioSctpChannel;
- NioSocketChannel。
1.2 EventLoop 接口
EventLoop 定义了 Netty 的核心抽象,用于处理连接的生命周期中所发生的事件。如图在高层次上说明了 Channel、EventLoop、Thread 以及 EventLoopGroup 之间的关系。

这些关系可以表述为:
- 一个 EventLoopGroup 包含一个或者多个 EventLoop;
- 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
- 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
- 一个 Channel 在它的生命周期内只注册于一个 EventLoop;
- 一个 EventLoop 可能会被分配给一个或多个 Channel。
注意,在这种设计中,一个给定 Channel 的 I/O 操作都是由相同的 Thread 执行的,实际上消除了对于同步的需要。
1.3 ChannelFuture 接口
Netty 中所有的 I/O 操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此,Netty 提供了ChannelFuture 接口,其 addListener()方法注册了一个ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。
可以将 ChannelFuture 看作是将来要执行的操作的结果的占位符。它究竟什么时候被执行则可能取决于若干的因素,因此不可能准确地预测,但是可以肯定的是它将会被执行。此外,所有属于同一个 Channel 的操作都被保证其将以它们被调用的顺序被执行。
二、ChannelHandler 和 ChannelPipeline
2.1 ChannelHandler 接口
从应用程序开发人员的角度来看,Netty 的主要组件是 ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。这是可行的,因为 ChannelHandler 的方法是由网络事件(其中术语“事件”的使用非常广泛)触发的。事实上,ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另外一种格式,或者处理转换过程中所抛出的异常。
举例来说,ChannelInboundHandler 是一个你将会经常实现的子接口。这种类型的ChannelHandler 接收入站事件和数据,这些数据随后将会被你的应用程序的业务逻辑所处理。当你要给连接的客户端发送响应时,也可以从 ChannelInboundHandler 冲刷数据。你的应用程序的业务逻辑通常驻留在一个或者多个 ChannelInboundHandler 中。
Netty 以适配器类的形式提供了大量默认的 ChannelHandler 实现,其旨在简化应用程序处理逻辑的开发过程。如ChannelPipeline中的每个ChannelHandler将负责把事件转发到链中的下一个 ChannelHandler。这些适配器类(及它们的子类)将自动执行这个操作,所以你可以只重写那些你想要特殊处理的方法和事件。
那么为什么要用适配器的形式提供这些?
那是因为有一些适配器类可以将编写自定义的 ChannelHandler 所需要的努力降到最低限度,因为它们提供了定义在对应接口中的所有方法的默认实现。下面这些是编写自定义 ChannelHandler 时经常会用到的适配器类:
- ChannelHandlerAdapter
- ChannelInboundHandlerAdapter
- ChannelOutboundHandlerAdapter
- ChannelDuplexHandler
2.2 ChannelPipeline 接口
ChannelPipeline 提供了 ChannelHandler 链的容器,并定义了用于在该链上传播入站和出站事件流的 API。当 Channel 被创建时,它会被自动地分配到它专属的 ChannelPipeline。ChannelHandler 安装到 ChannelPipeline 中的过程如下所示:
- 一个ChannelInitializer的实现被注册到了ServerBootstrap中或用于客户端的Bootstrap
- 当 ChannelInitializer.initChannel()方法被调用时,ChannelInitializer将在 ChannelPipeline 中安装一组自定义的 ChannelHandler;
- ChannelInitializer 将它自己从 ChannelPipeline 中移除。
为了审查发送或者接收数据时将会发生什么,让我们来更加深入地研究 ChannelPipeline和 ChannelHandler 之间的共生关系吧。
ChannelHandler 是专为支持广泛的用途而设计的,可以将它看作是处理往来 ChannelPipeline 事件(包括数据)的任何代码的通用容器。如图,其展示了从 ChannelHandler 派生的 ChannelInboundHandler 和ChannelOutboundHandler 接口。

使得事件流经 ChannelPipeline 是 ChannelHandler 的工作,它们是在应用程序的初始化或者引导阶段被安装的。这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个 ChannelHandler(有点类似责任链模式)。它们的执行顺序是由它们被添加的顺序所决定的。实际上,被我们称为 ChannelPipeline 的是这些 ChannelHandler 的编排顺序。
如图,说明了一个 Netty 应用程序中入站和出站数据流之间的区别。从一个客户端应用程序的角度来看,如果事件的运动方向是从客户端到服务器端,那么我们称这些事件为出站的,反之则称为入站的。

从上图看入站和出站 ChannelHandler 可以被安装到同一个 ChannelPipeline中。如果一个消息或者任何其他的入站事件被读取,那么它会从 ChannelPipeline 的头部开始流动,并被传递给第一个 ChannelInboundHandler。这个 ChannelHandler 不一定会实际地修改数据,具体取决于它的具体功能,在这之后,数据将会被传递给链中的下一个ChannelInboundHandler。最终,数据将会到达 ChannelPipeline 的尾端,届时,所有处理就都结束了。
数据的出站运动(即正在被写的数据)在概念上也是一样的。在这种情况下,数据将从ChannelOutboundHandler 链的尾端开始流动,直到它到达链的头部为止。在这之后,出站数据将会到达网络传输层,这里显示为 Socket。通常情况下,这将触发一个写操作。
ps:通过使用作为参数传递到每个方法的 ChannelHandlerContext事件可以被传递给当前ChannelHandler 链中的下一个ChannelHandler。因为你有时会忽略那些不感兴趣的事件,所以 Netty提供了抽象基类
ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter。 ChannelHandlerContext 上的对应方法,每个都提供了简单地将事件传递给下一ChannelHandler的方法的实现。随后,你可以通过重写你所感兴趣的那些方法来扩展这些类。
上图中出站和入站的ChannelHandler都在同一个ChannelPipeline中,那么ChannelPipeline是如何区分和处理这两种不同的类别的呢?
虽然 ChannelInboundHandle 和ChannelOutboundHandle 都扩展自 ChannelHandler,但是 Netty 能区分 ChannelInboundHandler 实现和 ChannelOutboundHandler 实现,并确保数据只会在具有相同定向类型的两个 ChannelHandler 之间传递。
当ChannelHandler 被添加到ChannelPipeline 时,它将会被分配一个ChannelHandlerContext,其代表了 ChannelHandler 和 ChannelPipeline 之间的绑定。虽然这个对象可以被用于获取底层的 Channel,但是它主要还是被用于写出站数据。
在 Netty 中,有两种发送消息的方式。你可以直接写到 Channel 中,也可以 写到和 ChannelHandler相关联的ChannelHandlerContext对象中。前一种方式将会导致消息从ChannelPipeline 的尾端开始流动,而后者将导致消息从 ChannelPipeline 中的下一个 ChannelHandler 开始流动。
总结一下:
- 将消息写入Channel 它将从尾端开始流动。
- 将消息写入ChannelHandler中,它将会从下一个ChannelHandler开始流动。
2.3 编码器和解码器
当你通过 Netty 发送或者接收一个消息的时候,就将会发生一次数据转换。入站消息会被解码;也就是说,从字节转换为另一种格式,通常是一个 Java 对象。如果是出站消息,则会发生相反方向的转换:它将从它的当前格式被编码为字节。这两种方向的转换的原因很简单:网络数据总是一系列的字节。(编解码)
对应于特定的需要,Netty 为编码器和解码器提供了不同类型的抽象类。例如,你的应用程序可能使用了一种中间格式,而不需要立即将消息转换成字节。你将仍然需要一个编码器,但是它将派生自一个不同的超类。为了确定合适的编码器类型,你可以应用一个简单的命名约定。通常来说,这些基类的名称将类似于 ByteToMessageDecoder 或 MessageToByteEncoder。对于特殊的类型,你可能会发现类似于 ProtobufEncoder 和 ProtobufDecoder这样的名称——预置的用来支持 Google 的 Protocol Buffers。
严格地说,其他的处理器也可以完成编码器和解码器的功能。但是,正如有用来简化ChannelHandler 的创建的适配器类一样,所有由 Netty 提供的编码器/解码器适配器类都实现了 ChannelOutboundHandler 或者 ChannelInboundHandler 接口。
你将会发现对于入站数据来说,channelRead 方法/事件已经被重写了。对于每个从入站Channel 读取的消息,这个方法都将会被调用。随后,它将调用由预置解码器所提供的 decode()方法,并将已解码的字节转发给 ChannelPipeline 中的下一个 ChannelInboundHandler。
出站消息的模式是相反方向的:编码器将消息转换为字节,并将它们转发给下一个ChannelOutboundHandler。
2.4 抽象类 SimpleChannelInboundHandler
最常见的情况是,你的应用程序会利用一个 ChannelHandler 来接收解码消息,并对该数据应用业务逻辑。要创建一个这样的 ChannelHandler,你只需要扩展基类 SimpleChannelInboundHandler,其中 T 是你要处理的消息的 Java 类型 。在这个 ChannelHandler 中,你将需要重写基类的一个或者多个方法,并且获取一个到 ChannelHandlerContext 的引用,这个引用将作为输入参数传递给 ChannelHandler 的所有方法。
在这种类型的 ChannelHandler 中,最重要的方法是 channelRead0(ChannelHandlerContext,T)。除了要求不要阻塞当前的 I/O 线程之外,其具体实现完全取决于你。
三、引导
Netty 的引导类为应用程序的网络层配置提供了容器,这涉及将一个进程绑定到某个指定的端口(服务端),或者将一个进程连接到另一个运行在某个指定主机的指定端口上的进程(客户端)。
严格来说,“连接”这个术语仅适用于面向连接的协议,如 TCP,其保证了两个连接端点之间消息的有序传递。
因此,有两种类型的引导:一种用于客户端(简单地称为 Bootstrap),而另一种(ServerBootstrap)用于服务器。无论你的应用程序使用哪种协议或者处理哪种类型的数据,唯一决定它使用哪种引导类的是它是作为一个客户端还是作为一个服务器(后面我们单独提出来说明引导)。
| 类别 | Bootstrap | ServerBootstrap |
|---|---|---|
| 网络编程中的作用 | 连接到远程主机和端口 | 绑定到一个本地端口 |
| EventLoopGroup 的数目 | 1 | 2 |
ps:实际上,ServerBootstrap 类也可以只使用一个 EventLoopGroup,此时其将在两个场景下共用同一个 EventLoopGroup。
细心的同学应该发现了,ServerBootstrap使用了2个EventLoopGroup,这是因为服务器需要两组不同的 Channel。
- 第一组将只包含一个 ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字。(专门用来创建Channel )
- 而第二组将包含所有已创建的用来处理传入客户端连接(对于每个服务器已经接受的连接都有一个)的 Channel。(专门为Channel分配EventLoop)
它们的关系如图:

ServerChannel 相关联的 EventLoopGroup 将分配一个负责为传入连接请求创建Channel 的 EventLoop。一旦连接被接受,第二个 EventLoopGroup 就会给它的 Channel分配一个 EventLoop。
Netty实战(三)的更多相关文章
- Netty实战三之Netty的组件和设计
有关Netty,我们可以从两个视角来讨论Netty:类库的视角以及框架的视角,对于使用Netty编写高效的.可重用的和可维护的代码来说,两者缺一不可. Netty解决了两个响应的关注领域,可以大致标志 ...
- 重磅!阿里P8费心整理Netty实战+指南+项目白皮书PDF,总计1.08G
前言 Netty是一款用于快速开发高性能的网络应用程序的Java框架.它封装了网络编程的复杂性,使网络编程和Web技术的最新进展能够被比以往更广泛的开发人员接触到. Netty不只是一个接口和类的集合 ...
- coreseek实战(三):全文搜索在php中应用(使用api接口)
coreseek实战(三):全文搜索在php中应用(使用api接口) 这一篇文章开始学习在php页面中通过api接口,使用coreseek全文搜索. 第一步:综合一下前两篇文章,coreseek实战( ...
- Python爬虫实战三之实现山东大学无线网络掉线自动重连
综述 最近山大软件园校区QLSC_STU无线网掉线掉的厉害,连上之后平均十分钟左右掉线一次,很是让人心烦,还能不能愉快地上自习了?能忍吗?反正我是不能忍了,嗯,自己动手,丰衣足食!写个程序解决掉它! ...
- 自顶向下深入分析Netty(三)--Bootstrap
自顶向下深入分析Netty(一)--预备知识 自顶向下深入分析Netty(二)--线程模型 自顶向下深入分析Netty(三)--Bootstrap 自顶向下深入分析Netty(四)--EventLoo ...
- 《Netty实战》源码运行及本地环境搭建
1.源码路径: GitHub - zzzvvvxxxd/netty-in-action-cn: Netty In Action 中文版 ,中文唯一正版<Netty实战>的代码清单 下载后 ...
- 1.Netty 实战前言
1.参考文档:Netty实战精髓篇 2.Netty介绍: Netty是基于Java NIO的网络应用框架. Netty是一个NIO client-server(客户端服务器)框架,使用Nett ...
- Thrift RPC实战(三) thrift序列化揭秘
本文主要讲解Thrift的序列化机制, 看看thrift作为数据交换格式是如何工作的? 1.构造应用场景: 1). 首先我们先来定义下thrift的简单结构. 1 2 3 4 5 namespace ...
- miniFTP项目实战三
项目简介: 在Linux环境下用C语言开发的Vsftpd的简化版本,拥有部分Vsftpd功能和相同的FTP协议,系统的主要架构采用多进程模型,每当有一个新的客户连接到达,主进程就会派生出一个ftp服务 ...
- 1、Netty 实战入门详解
一.Netty 简介 Netty 是基于 Java NIO 的异步事件驱动的网络应用框架,使用 Netty 可以快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程 ...
随机推荐
- 代码大全_V2(1,2章笔记)
译序 这本书讲什么 代码大全 原名叫 code complete,它是什么,又不是什么? 不是IDE中的代码自动补全功能 不是软件源代码 "大全" 是 "编码完成&quo ...
- QGIS 导入文本数据(WKT)
在做GIS数据处理的时候,经常会遇到原始数据是 text.csv.Excel 等格式的数据.要使用数据前提是要先转换数据. 这里是介绍用 QGIS 导入数据.打开导入方式如下(根据自己的文本类型选择不 ...
- 泰拉瑞亚EasyBuildMod便捷建造模组开发详细过程
pre { overflow-y: auto; max-height: 400px } img { max-width: 500px; max-height: 300px } github地址: ht ...
- ArgoCD实践之基于配置清单创建Application
1. 什么是Application ArgoCD的两个核心概念为Application和Project,他们可分别基于Application CRD和AppProject CRD创建; 核心组件: A ...
- C#/VB.NET:如何将PDF转为PDF/A
PDF/A是一种ISO标准的PDF文件格式版本,是为长期保存文件而设计的.它提供了一种工具,使电子文件在长时间之后依然以一种保留其外观的方式重现,而不管该文件是用什么工具和系统创建.储存或制作的.这种 ...
- 全网最详细中英文ChatGPT-GPT-4示例文档-智能AI写作从0到1快速入门——官网推荐的48种最佳应用场景(附python/node.js/curl命令源代码,小白也能学)
目录 Introduce 简介 setting 设置 Prompt 提示 Sample response 回复样本 API request 接口请求 python接口请求示例 node.js接口请求示 ...
- mysql的concat与concat_ws拼接字符串的使用
concat的使用 可以拼接多个字符 mysql> select concat(name,dept,job) from t1; +-----------------------+ | conca ...
- IIC总线协议—读写EEPROM
IIC总线协议-读写EEPROM 1.I2C简介 I2C 通讯协议(Inter-Integrated Circuit)是由Phiilps公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要USA ...
- linux 安装 node 和 npm 服务
1.安装文件下载 下载地址:https://nodejs.org/zh-cn/download/ 2.安装步骤 1.将安装包上传到指定位置(我习惯放到:/usr/local/application/目 ...
- 如何在模型中引入可学习参数(Pytorch)
错误实例: def init(self): self.w1 = torch.nn.Parameter(torch.FloatTensor(1),requires_grad=True).cuda() s ...