深入理解Netty及核心组件使用—中

目录

EventLoop和EventLoopGroup

线程的分配

线程管理

Channel、EventLoop(Group)和ChannelFuture

Channel接口

Channel 的生命周期状态

重要 Channel 的方法

ChannelPipeline和ChannelHandlerContext

ChannelPipeline接口

ChannelHandler 的生命周期

ChannelPipeline中的ChannelHandler

ChannelPipeline上的方法

ChannelHandlerContext

Channel、ChannelPipeline 和 ChannelHandlerContext上的事件传播

ChannelHandlerContext 的 API 


EventLoop和EventLoopGroup

       在一个 while 循环中 select 出事 件,然后依次处理每种事件。我们可以把它称为事件循环,这就是 EventLoop。interface io.netty.channel. EventLoop 定义了 Netty 的核心抽象,用于处理网络连接的生命周期中所发生的事件。

       io.netty.util.concurrent 包构建在 JDK 的 java.util.concurrent 包上。而 io.netty.channel 包中的类,为了与 Channel 的事件进行交互,扩展了这些接口/类。一个 EventLoop 将由一个永远都不会改变的 Thread 驱动,同时任务(Runnable 或者 Callable)可以直接提交给 EventLoop 实现,以立即执行或者调度执行。


线程的分配

       服务于 Channel 的 I/O 和事件的 EventLoop 包含在 EventLoopGroup 中。

       异步传输实现只使用了少量的 EventLoop(以及和它们相关联的 Thread),而且在当前的线程模型中,它们可能会被多个 Channel 所共享。这使得可以通过尽可能少量的 Thread 来支撑大量的 Channel,而不是每个 Channel 分配一个 Thread。EventLoopGroup 负责为每个新创建的 Channel 分配一个 EventLoop。在当前实现中,使用顺序循环(round-robin)的方式进行分配以获取一个均衡的分布,并且相同的 EventLoop 可能会被分配给多个 Channel。

        一旦一个 Channel 被分配给一个 EventLoop,它将在它的整个生命周期中都使用这个 EventLoop(以及相关联的 Thread)。

        

         一个 EventLoop 通常会被用于支撑多个 Channel,所以对于所有相关联的 Channel 来说,ThreadLocal 都将是一样的。这使得它对于实现状态追踪等功能来说不是很好的选择。在一些无状态的上下文中,它仍然可以被用于在多个 Channel 之间共享一些重度的或者代价昂贵的对象,甚 至是事件。


线程管理

       在内部,当提交任务到如果(当前)调用线程正是支撑 EventLoop 的线程,那么所提交的代码块将会被(直接)执行。否则,EventLoop 将调度该任务以便稍后执行,并将它放入到内部队列中。当 EventLoop 下次处理它的事件时,它会执行队列中的那些任务/事件。

          


Channel、EventLoop(Group)和ChannelFuture

Netty 网络抽象:

Channel—Socket;EventLoop—控制流、多线程处理、并发;ChannelFuture—异步通知。

Channel 和 EventLoop 关系如下:

                

        可以看出 Channel 需要被注册到某个 EventLoop 上,在 Channel 整个生命周期内都由这个EventLoop处理IO事件,也就是说一个Channel和一个EventLoop进行了绑定,但是一个EventLoop可以同时被多个Channel绑定。


Channel接口

       基本的 I/O 操作(bind()、connect()、read()和 write())依赖于底层网络传输所提供的原语。在基于 Java 的网络编程中,其基本的构造是类 Socket。Netty 的 Channel 接口所提供的 API,被用于所有的 I/O 操作。大大地降低了直接使用 Socket 类的复杂性。此外,Channel 也是拥有许多预定义的、专门化实现的广泛类层次结构的根。

Channel 的生命周期状态

ChannelUnregistered :Channel 已经被创建,但还未注册到 EventLoop
ChannelRegistered :Channel 已经被注册到了 EventLoop
ChannelActive :Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接
收和发送数据了
ChannelInactive :Channel 没有连接到远程节点
        当这些状态发生改变时,将会生成对应的事件。这些事件将会被转发给ChannelPipeline
中的ChannelHandler,其可以随后对它们做出响应。

重要 Channel 的方法

eventLoop:返回分配给 Channel 的 EventLoop。

pipeline: 返回 Channel 的 ChannelPipeline,也就是说每个 Channel 都有自己的
ChannelPipeline。
isActive: 如果 Channel 是活动的,则返回 true。活动的意义可能依赖于底层的传输。
例如,一个 Socket 传输一旦连接到了远程节点便是活动的,而一个 Datagram 传输一旦被
打开便是活动的。
localAddress:返回本地的 SokcetAddress
remoteAddress:返回远程的 SocketAddress
write:将数据写到远程节点,注意,这个写只是写往 Netty 内部的缓存,还没有真正写往 socket。
flush:将之前已写的数据冲刷到底层 socket 进行传输。
writeAndFlush:一个简便的方法,等同于调用 write()并接着调用 flush()。


ChannelPipeline和ChannelHandlerContext

ChannelPipeline接口

       当 Channel 被创建时,它将会被自动地分配一个新的 ChannelPipeline,每个 Channel 都有自己的 ChannelPipeline。这项关联是永久性的。在 Netty 组件的生命周期中,这是一项固定的操作,不需要开发人员的任何干预。

       ChannelPipeline 提供了 ChannelHandler 链的容器,并定义了用于在该链上传播入站(也就是从网络到业务处理)和出站(也就是从业务处理到网络),各种事件流的 API,我们代码中的 ChannelHandler 都是放在 ChannelPipeline 中的。

        这些 ChannelHandler 对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个 ChannelHandler,而且 ChannelHandler 对象也完全可以拦截事件不让事件继续传递。它们的执行顺序是由它们被添加的顺序所决定的。

ChannelHandler 的生命周期

       在ChannelHandler 被添加到 ChannelPipeline 中或者被从 ChannelPipeline 中移除时会调用下面这些方法。这些方法中的每一个都接受一个 ChannelHandlerContext 参数。

handlerAdded 当把 ChannelHandler 添加到 ChannelPipeline 中时被调用。

handlerRemoved 当从 ChannelPipeline 中移除 ChannelHandler 时被调用。

exceptionCaught 当处理过程中在 ChannelPipeline 中有错误产生时被调用。

ChannelPipeline中的ChannelHandler

       入站和出站 ChannelHandler 被安装到同一个 ChannelPipeline 中,ChannelPipeline 以双 向链表的形式进行维护管理。

        如图,我们在网络上传递的数据,要求加密,但是加密后密文比较大,需要压缩后再传输,而且按照业务要求,需要检查报文中携带的用户信息是否合法,于是我们实现了 5 个 Handler:解压(入)Handler、压缩(出)handler、解密(入)Handler、加密(出)Handler、授权(入) Handler。

        如果一个消息或者任何其他的入站事件被读取,那么它会从 ChannelPipeline 的头部开始流动,但是只被处理入站事件的 Handler 处理,也就是解压(入)Handler、解密(入)Handler、 授权(入)Handler,最终,数据将会到达 ChannelPipeline 的尾端,届时,所有处理就都结束了。

        数据的出站运动(即正在被写的数据)在概念上也是一样的。在这种情况下,数据将从链的尾端开始流动,但是只被处理出站事件的 Handler 处理,也就是加密(出)Handler、压缩(出)handler,直到它到达链的头部为止。在这之后,出站数据将会到达网络传输层, 也就是我们的 Socket。

       Netty 能区分入站事件的 Handler 和出站事件的 Handler,并确保数据只会在具有相同定向类型的两个 ChannelHandler 之间传递。

       编写 Netty 应用程序时要注意,分属出站和入站不同的 Handler ,在业务没 特殊要求的情况下是无所谓顺序的,如图所示,比如‘压缩(出)handler‘可 以放在‘解压(入)handler‘和‘解密(入)Handler‘中间,也可以放在‘解密(入)Handler ‘和‘授权(入)Handler‘之间。而同属一个方向的 Handler 则是有顺序的,因为上一个 Handler 处理的结果往往是下一 个 Handler 的要求的输入。比如入站处理,对于收到的数据,只有先解压才能得到密文,才能解密,只有解密后才能拿到明文中的用户信息进行授权检查,所以解压->解密->授权这个三个入站 Handler 的顺序就不能乱。


ChannelPipeline上的方法

        ChannelPipeline 以双向链表的形式进行维护管理 Handler,自然也提供了对应的方法在 ChannelPipeline 中增加或者删除、替换 Handler。

addFirst、addBefore、addAfter、addLast:

        将一个 ChannelHandler 添加到 ChannelPipeline 中。

remove:将一个 ChannelHandler 从 ChannelPipeline 中移除。

replace:将 ChannelPipeline 中的一个 ChannelHandler 替换为另一个 ChannelHandler。

get:通过类型或者名称返回 ChannelHandler。

context:返回和 ChannelHandler 绑定的 ChannelHandlerContext。

names:返回 ChannelPipeline 中所有 ChannelHandler 的名称。


ChannelHandlerContext

        ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之间的关联,每当有 ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandlerContext,ChannelPipeline 以双向链表的形式进行维护管理 Handler,Handler 在放入 ChannelPipeline 的时候必须要有两个指针 pre 和 next 来说明它的前一个元素和后一个元素,但是 Handler 本身来维护这两个指针并不合适。我们在使用 JDK 的 LinkedList 的时候,我们放入 LinkedList 的数据是不会带这两个指针的,LinkedList 内部会用类 Node 对我们的数据进行包装,而类 Node 则带有两个指针 pre 和 next。所以,ChannelHandlerContext 的主要作用就和 LinkedList 内部的类 Node 类似。

        ChannelHandlerContext 不仅仅只是个包装类,它还提供了很多的方法,比如让事件从当前 ChannelHandler 传递给链中的下一个 ChannelHandler,还可以被用于获取底层的 Channel,还可以用于写出站数据。


Channel、ChannelPipeline 和 ChannelHandlerContext上的事件传播

       ChannelHandlerContext 有很多的方法,其中一些方法也存在于 Channel 和 Channel-Pipeline 身上,但是有一点重要的不同。如果调用Channel 或者ChannelPipeline 上的这些方法,它们将沿着整个 ChannelPipeline 进行传播。而调用位于 ChannelHandlerContext 上的相同方法,则将从当前所关联的 ChannelHandler 开始,并且只会传播给位于该 ChannelPipeline 中的下一个(入站下一个,出站上一个)能够处理该事件的 ChannelHandler。


ChannelHandlerContext 的 API
alloc 返回和这个实例相关联的 Channel 所配置的 ByteBufAllocator。
bind 绑定到给定的 SocketAddress,并返回 ChannelFuture。
channel 返回绑定到这个实例的 Channel。
close 关闭 Channel,并返回 ChannelFuture。
connect 连接给定的 SocketAddress,并返回 ChannelFuture。
deregister 从之前分配的 EventExecutor 注销,并返回 ChannelFuture。
disconnect 从远程节点断开,并返回 ChannelFuture。
executor 返回调度事件的 EventExecutor。
fireChannelActive 触发对下一个 ChannelInboundHandler 上的 channelActive()方法(已连接)的调用。
fireChannelInactive 触发对下一个 ChannelInboundHandler 上的 channelInactive()方法(已关闭)的调用。
fireChannelRead 触发对下一个 ChannelInboundHandler 上的 channelRead()方法(已接收的消息)的调用。
fireChannelReadComplete 触发对下一个 ChannelInboundHandler 上的channelReadComplete()方法的调用。
fireChannelRegistered 触发对下一个 ChannelInboundHandler 上的fireChannelRegistered()方法的调用
fireChannelUnregistered 触发对下一个 ChannelInboundHandler 上的fireChannelUnregistered()方法的调用
fireChannelWritabilityChanged 触发对下一个ChannelInboundHandler上的fireChannelWritabilityChanged()方法的调用。
fireExceptionCaught 触发对下一个 ChannelInboundHandler 上的fireExceptionCaught(Throwable)方法的调用。
fireUserEventTriggered 触发对下一个 ChannelInboundHandler上的fireUserEventTriggered(Object evt)方法的调用。
handler 返回绑定到这个实例的 ChannelHandler。
isRemoved 如果所关联的 ChannelHandler 已经被从 ChannelPipeline 中移除则返回 true。
name 返回这个实例的唯一名称。
pipeline 返回这个实例所关联的 ChannelPipeline
read 将数据从 Channel 读取到第一个入站缓冲区;如果读取成功则触发一个channelRead 事件,并(在最后一个消息被读取完成后)通知 ChannelInboundHandler 的channelReadComplete(ctx)方法。
write 通过这个实例写入消息并经过 ChannelPipeline。
writeAndFlush 通过这个实例写入并冲刷消息并经过ChannelPipeline。

当使用 ChannelHandlerContext 的 API 的时候,有以下两点:

1. ChannelHandlerContext 和ChannelHandler 之间的关联(绑定)是永远不会改变的,所以缓存对它的引用是安全的。

2. 相对于其他类的同名方法,ChannelHandlerContext 的方法将产生更短的事件流,应该尽可能地利用这个特性来获得最大的性能。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# OpenVINO 图片旋转角度检测

目录 效果 项目 代码 下载 效果 项目 代码 using OpenCvSharp; using Sdcb.OpenVINO; using System; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Te…

[软件工具]文档页数统计工具软件pdf统计页数word统计页数ppt统计页数图文打印店快速报价工具

文档页数统计工具软件——打印方面好帮手 在信息化时代,文档已成为我们工作、学习、生活中不可或缺的一部分。无论是学术论文、商业报告,还是个人日记,都需要我们对其进行有效的管理。而在这个过程中,文档页数统计工具软件就显得…

tee漏洞学习-翻译-2:探索 Qualcomm TrustZone的实现

原文:http://bits-please.blogspot.com/2015/08/exploring-qualcomms-trustzone.html 获取 TrustZone image 从两个不同的位置提取image 从手机设备本身从google factory image 已经root的Nexus 5设备,image存储在eMMC芯片上,并且eMMC芯片…

【网站项目】039菜匣子优选生鲜电商系统

🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板&#xff…

LeetCode、790. 多米诺和托米诺平铺【中等,二维DP,可转一维】

文章目录 前言LeetCode、790. 多米诺和托米诺平铺【中等,二维DP,可转一维】题目与分类思路二维解法二维转一维 资料获取 前言 博主介绍:✌目前全网粉丝2W,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质…

数据库学习笔记2024/2/5

2. SQL 全称 Structured Query Language,结构化查询语言。操作关系型数据库的编程语言,定义了 一套操作关系型数据库统一标准 2.1 SQL通用语法 在学习具体的SQL语句之前,先来了解一下SQL语言的通用语法。 1). SQL语句可以单行或多行书写&…

MySQL学习一、库和表的基础操作

目录 一、常用数据类型 1.数值类型 2.字符串类型 3.日期类型 ​二、数据库的基础操作 三、表的基础操作 一、常用数据类型 1.数值类型 数值类型可以指定为无符号(unsigned ),但不建议取 2.字符串类型 3.日期类型 二、数据库的基础操作…

CentOS7搭建k8s-v1.28.6集群详情

文章目录 1.灌装集群节点操作系统1.1 设置hosts1.2 设置nameserver1.3 关闭防火墙1.4 关闭Selinux1.5 关闭Swap分区1.6 时间同步1.7 调整内核参数1.8 系统内核升级 2.安装Docker2.1 卸载旧Docker2.2 配置Docker软件源2.3 安装Docker 3.部署Kubernets集群3.1 设置 K8s 软件源3.2…

Node.js JSON Schema Ajv依赖库逐步介绍验证类型和中文错误提示

在构建应用程序时,数据的有效性是至关重要的。为了确保传入的数据符合预期的格式和规范,我们可以使用 Ajv(Another JSON Schema Validator)进行验证。在这篇博文中,我们将从头开始学习 Ajv,逐步介绍验证类型…

如何在Windows系统上部署docker

上次在Windows系统上部署成功Ubuntu系统,这次准备在Windows上部署docker desktop应用 这个应用软件类似于虚拟机,可以在该应用软件上部署多个镜像容器。其最直观的表现就是可以借用Windows和Ubuntu终端来访问docker“模拟的系统”。 Docker简介 Docke…

OCR升级版 — 微调EasyOCR实战

OCR是从图像中提取文本的有价值工具。然而,有时您使用的OCR在特定需求上的表现不如您所希望的那样好。如果您面临这样的问题,微调OCR引擎是解决的一种方法。在本教程中,我将向您展示如何微调EasyOCR,这是一个免费、开源的OCR引擎&…

Golang-Map有序输出——使用orderedmap库实现

前言 工作中遇到一个问题:需要导出一个MySQL表格,表格内容由sql查询得来。但现在发现,所导出的表格中,各列的顺序不确定。多次导出, 每一次的序列顺序也是不定的。 因此确定是后端,Map使用相关导致的问题。…