Netty实战(三)

news2024/11/26 20:20:29

Netty的组件和设计

  • 一、Channel、EventLoop 和 ChannelFuture
    • 1.1 Channel 接口
    • 1.2 EventLoop 接口
    • 1.3 ChannelFuture 接口
  • 二、ChannelHandler 和 ChannelPipeline
    • 2.1 ChannelHandler 接口
    • 2.2 ChannelPipeline 接口
    • 2.3 编码器和解码器
    • 2.4 抽象类 SimpleChannelInboundHandler
  • 三、引导

一、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)用于服务器。无论你的应用程序使用哪种协议或者处理哪种类型的数据,唯一决定它使用哪种引导类的是它是作为一个客户端还是作为一个服务器。

类别BootstrapServerBootstrap
网络编程中的作用连接到远程主机和端口绑定到一个本地端口
EventLoopGroup 的数目12

ps:实际上,ServerBootstrap 类也可以只使用一个 EventLoopGroup,此时其将在两个场景下共用同一个 EventLoopGroup

细心的同学应该发现了,ServerBootstrap使用了2个EventLoopGroup,这是因为服务器需要两组不同的 Channel。

  • 第一组将只包含一个 ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字。(专门用来创建Channel )
  • 而第二组将包含所有已创建的用来处理传入客户端连接(对于每个服务器已经接受的连接都有一个)的 Channel。(专门为Channel分配EventLoop)

它们的关系如图:
在这里插入图片描述
ServerChannel 相关联的 EventLoopGroup 将分配一个负责为传入连接请求创建Channel 的 EventLoop。一旦连接被接受,第二个 EventLoopGroup 就会给它的 Channel分配一个 EventLoop。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/533803.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

suricata中DPDK收发包源码分析2

《suricata中DPDK收发包源码分析1》中分析了整体的DPDK收发包框架代码,今天我们继续来深入了解一下一些细节方面的问题。 目录 Q1:收发包线程模式在代码中是怎样确定的? Q2: DPDK库的初始化rte_eal_init在哪里调用的? Q3: 对网…

Linux中LV Status的状态为NOT available

今天下午有现场反馈备份磁盘找不到了,使用lvm方式的。提供了todesk帮忙看下, 首先使用 blkid查看,确实看不到备份磁盘的UUID,使用lvdisplay查看状态,状态不对了 [rootdb1 ~]# lvdisplay --- Logical volume --- …

.Vue3项目初始化

文章目录 1.Vue3项目初始化1.1 创建vue项目1.2 vue 初始化1.3 git 项目管理1.4 配置iconfig.json1.5 element 按需引入1.6 element 主题色的定制1.7 axios的基础配置1.8 router路由的配置 1.Vue3项目初始化 1.1 创建vue项目 npm init vuelatest1.2 vue 初始化 npm install1.…

【2023/05/16】MonteCarlo

Hello!大家好,我是霜淮子,2023倒计时第11天。 Share O Beauty,find theyself in love,not in the flattery of thymirror. 译文: 啊,美啊,在爱中找你自己吧,不要到你镜子的诌谀中去寻找。 M…

[遗传学]转座因子的结构与功能

本篇文章主要带你了解:转座因子的发现和分类;原核生物以及真核生物种的转座子;转座作用的分子机制以及转座因子的遗传学效应和应用. 🧬转座因子的发现和分类 🧬转座因子的概念 转座因子(transposable element)是在转座酶(transposase&#xf…

Class 03 - R语言的 Vectors(向量) 与 lists(列表)

Class 03 - R语言的 Vector与 列表 list R语言语法脚本文件的创建、保存、和修改名称第一个函数使用帮助功能查看函数详细说明语法问题变量与赋值定义变量名称格式调用变量 R中的数据结构Vectors (向量)创建向量查看向量的性质查看数据类型 typeof()查看数据长度 length()检查…

Elasticsearch 核心技术(十):GEO 地理查询(geo_bounding_box、geo_distance、geo_shape)

❤️ 博客主页:水滴技术 🚀 支持水滴:点赞👍 收藏⭐ 留言💬 🌸 订阅专栏:大数据核心技术从入门到精通 文章目录 一、地理数据类型1.1、geo_point 地理点类型1.1.1、创建一个含有 geo_point 字…

opencv_c++学习(八)

一、两张图像的像素比较 比较最大最小 最小: min(lnputArray src1, InputArray src2, outputArray dst)最大: max(lnputArray src1, InputArray src2, outputArray dst)src1 :第一个图像矩阵,可以是任意通道数的矩阵。 src2:第二个图像矩…

电源电压过冲

前言: 前段时间突然想起来以前的一个问题,这个问题相信大家也都遇到过,甚至是解决过,或者没解决,也就不了了之,今天这篇文章,主要来讲下这个问题,看完喜欢的欢迎给我留言或者点赞&a…

【Linux】常见指令

🌇个人主页:平凡的小苏 📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情 🛸C专栏:Linux修炼内功基地 家人们更新不易,你们的👍点赞👍和⭐关注⭐…

EasyYapi插件—快速生成API接口文档

EasyYapi插件—快速生成API接口文档 1. 功能 导出http到(Controller注解类) YapiPostmanmarkdown导出RPC到 YapimarkdownCall api调用API接口 注意点: 注释中可以使用module标注模块所属的模块。表示接口会发布到yapi模块下;只…

生存分析利器:Python 中的 Kaplan-Meier Fitter 类详解

KaplanMeierFitter是lifelines库中用于计算生存分析的一个类。使用KaplanMeierFitter类,我们可以对我们的数据进行不同组之间的生存分析,比如根据年龄、性别、治疗手段等分类变量进行分组分析。 该类包含许多方法和属性,其中最常用的是fit()…

FPGA复位信号设计讨论

复位概述 复位作为电子系统中最常见的信号同时也是最重要的信号,它对工程师整体的设计表现有着极大的影响。复位信号可能深刻影响设计的性能表现,功耗,面积等等。对于一个优秀的系统设计,很难不把复位信号当成一个关键信号来设计。…

【车载基础软件 |ASF中间件 系列二】— 国产汽车生态平台ASF的生态框架

本文主要介绍国产基础软件开发平台架构下基于ASF的生态框架。 背景信息 随着E/E架构演进,从最初传统的分布式架构,由独立功能的ECU通过CANLIN等高实时性 总线通讯。诼渐演进到当前分域集中式结合车联网功能的结构,车载以大网逐渐步入了整车电子电器架构之中,除了高实时性…

【AIGC使用教程】论文阅读神器 SciSpace 从注册到体验

欢迎关注【AIGC使用教程】 专栏 【AIGC使用教程】论文阅读神器 SciSpace 从注册到体验 【AIGC使用教程】Microsoft Edge/Bing Chat 注册使用完全指南 【AIGC使用教程】GitHub Copilot 免费注册及在 VS Code 中的安装使用 【AIGC使用教程】GitHub Copilot 免费注册及在 PyCharm …

golang第一个简单项目实战+源码(gin+gorm)

创建项目并新建包 点击file->settings->GOPATH,把当前项目加进GOPATH目录中,然后点击apply, 点击加号完成之后,在左边选择go build 新建数据库 编写配置信息 这里将数据的信息都写在了配置文件里面 dns的内容为&#x…

二叉树概念结构,以及画图加代码分析二叉树遍历,创建,销毁,层序遍历,判断是否完全二叉树等等

二叉树 树的概念及结构树的概念树的相关概念树的表示 二叉树的概念及结构概念特殊的二叉树二叉树性质二叉树的存储结构 二叉树的实现二叉树顺序结构的实现二叉树链式结构的实现二叉树的遍历前序遍历中序遍历后序遍历 二叉树结点数量叶子结点数量求树高求k层结点数量 二叉树创建…

SpringMVC第一阶段:SpringMVC的介绍和使用

1、SpringMVC的概述 Spring MVC框架是一个开源的Java平台,为开发强大的基于JavaWeb应用程序提供全面的基础架构支持,并且使用起来非常简单容易。 Spring web MVC框架提供了MVC(模型 - 视图 - 控制器)架构,用于开发灵活和松散耦合的Web应用程…

【原创】创建Vue3 ts项目

文章目录 创建Vue3 ts项目工作空间创建vue 项目进入项目目录启动项目 & 停止项目访问成功 创建Vue3 ts项目 工作空间 进入工作空间目录:D:\workspace\vue 创建vue 项目 vue create vt #创建vt项目vue test 选择手动进行配置: 选中下面几个核心…

利用 Mybatis-Plus 的动态数据源实现多数据源配置

目录 一、导入依赖 二、Application.yaml配置文件 三、切换数据源 四、其他方法 4.1 配置多个数据源 4.2 定义Datasource和EntityManager 4.3 在需要使用数据源的地方注入不同的EntityManager 官网:https://baomidou.com/pages/a61e1b/#dynamic-datasource …