③ChannelHandler,ChannelPipeline 和 ChannelHandlerContext
如果说 EventLoop 是事件的通知者,那么 ChannelHandler 就是事件的处理者 。
在 ChannelHandler 中可以添加一些业务代码,例如数据转换,逻辑运算等等 。
正如上面例子中展示的,Server 和 Client 分别都有一个 ChannelHandler 来处理,读取信息,网络可用,网络异常之类的信息 。
并且,针对出站和入站的事件,有不同的 ChannelHandler,分别是:
- ChannelInBoundHandler(入站事件处理器)
- ChannelOutBoundHandler(出站事件处理器)
文章插图
假设每次请求都会触发事件,而由 ChannelHandler 来处理这些事件,这个事件的处理顺序是由 ChannelPipeline 来决定的 。
文章插图
ChannelHanlder 处理,出站/入站的事件
ChannelPipeline 为 ChannelHandler 链提供了容器 。到 Channel 被创建的时候,会被 Netty 框架自动分配到 ChannelPipeline 上 。
ChannelPipeline 保证 ChannelHandler 按照一定顺序处理事件,当事件触发以后,会将数据通过 ChannelPipeline 按照一定的顺序通过 ChannelHandler 。
说白了,ChannelPipeline 是负责“排队”的 。这里的“排队”是处理事件的顺序 。
同时,ChannelPipeline 也可以添加或者删除 ChannelHandler,管理整个队列 。
文章插图
如上图,ChannelPipeline 使 ChannelHandler 按照先后顺序排列,信息按照箭头所示方向流动并且被 ChannelHandler 处理 。
说完了 ChannelPipeline 和 ChannelHandler,前者管理后者的排列顺序 。那么它们之间的关联就由 ChannelHandlerContext 来表示了 。
每当有 ChannelHandler 添加到 ChannelPipeline 时,同时会创建 ChannelHandlerContext。
ChannelHandlerContext 的主要功能是管理 ChannelHandler 和 ChannelPipeline 的交互 。
不知道大家注意到没有,开始的例子中 ChannelHandler 中处理事件函数,传入的参数就是 ChannelHandlerContext 。
文章插图
ChannelHandlerContext 参数贯穿 ChannelPipeline,将信息传递给每个 ChannelHandler,是个合格的“通讯员” 。
文章插图
ChannelHandlerContext 负责传递消息
把上面提到的几个核心组件归纳一下,用下图表示方便记忆他们之间的关系 。
文章插图
Netty 核心组件关系图
Netty 的数据容器前面介绍了 Netty 的几个核心组件,服务器在数据传输的时候,产生事件,并且对事件进行监控和处理 。
接下来看看数据是如何存放以及是如何读写的 。Netty 将 ByteBuf 作为数据容器,来存放数据 。
ByteBuf 工作原理
从结构上来说,ByteBuf 由一串字节数组构成 。数组中每个字节用来存放信息 。
ByteBuf 提供了两个索引,一个用于读取数据,一个用于写入数据 。这两个索引通过在字节数组中移动,来定位需要读或者写信息的位置 。
当从 ByteBuf 读取,它的 readerIndex(读索引)将会根据读取的字节数递增 。
同样,当写 ByteBuf 时,它的 writerIndex 也会根据写入的字节数进行递增 。
文章插图
ByteBuf 读写索引图例
需要注意的是极限的情况是 readerIndex 刚好读到了 writerIndex 写入的地方 。
如果 readerIndex 超过了 writerIndex 的时候,Netty 会抛出 IndexOutOf-BoundsException 异常 。
ByteBuf 使用模式
谈了 ByteBuf 的工作原理以后,再来看看它的使用模式 。
根据存放缓冲区的不同分为三类:
- 堆缓冲区,ByteBuf 将数据存储在 JVM 的堆中,通过数组实现,可以做到快速分配 。由于在堆上被 JVM 管理,在不被使用时可以快速释放 。可以通过 ByteBuf.array() 来获取 byte[] 数据 。
- 直接缓冲区,在 JVM 的堆之外直接分配内存,用来存储数据 。其不占用堆空间,使用时需要考虑内存容量 。它在使用 Socket 传递时性能较好,因为间接从缓冲区发送数据,在发送之前 JVM 会先将数据复制到直接缓冲区再进行发送 。由于,直接缓冲区的数据分配在堆之外,通过 JVM 进行垃圾回收,并且分配时也需要做复制的操作,因此使用成本较高 。
推荐阅读
- 学会使用 Spring Boot 的异步调用
- 脂肪通过什么排出?
- 用Apple Configurator 2从iOS复制软件
- 健康生活,从一杯清茶开始……
- 送茶叶代表什么意思?
- 程序安装包咋制作的?Qt程序打包三部曲,从应用程序到安装包
- 很多人学Spring框架,总觉得IOC模糊不清?
- 一个人如何开发一款app?
- 我的第一次安卓app开发经历
- 用python处理excel文件有多轻松?工作从未如此简单