Netty源码学习3——Channel ,ChannelHandler,ChannelPipeline
零丶引入
在Netty源码学习2——NioEventLoop的执行中,我们学习了NioEventLoop是如何进行事件循环以及如何修复NIO 空轮询的bug的,但是没有深入了解IO事件在netty中是如何被处理的,下面我们以服务端demo代码为例子,看下和IO事件处理密切的Channel

如上在编写netty 服务端的时候,我们一般只需要指定Channel类型,以及实现ChannelHandler在对应方法中编写业务逻辑代码即可。
在Netty中,NioEventLoop是事件的调度中心,它控制了Io事件和其他任务的调度,但是io事件的处理是依赖ChannelHandler的,多个ChannelHandler又由ChannelPipline组装成流水线依次执行
这篇博客我们以此为切入点,看看Channel是如何初始化的,如何和EventLoop关联起来的,后续看看ChannelPipline是如何组织ChannelHandler的。
一丶Channel概述
Channel是Netty抽象出来的对网络I/O进行读写的相关接口,与JDK NIO中的Channel接口类似。Channel的主要功能有网络I/O的读写,绑定端口,客户端发起连接、主动关闭连接、获取通信双方网络地址等。
下面是NioServerSocketChannel,和NioSocketChannel的类图

ChannelOutboundInvoker:定义了bind,connect,disconnect,close等方法。
Channel:Netty抽象出来的对网络I/O进行读写的相关接口,其中有两个关键的方法

可看出EventLoop和Channel的关系——Channel是注册到EventLoop中的。
ChannelPipeline和Channel的关系——每一个Channel会分配一个ChannelPipline。
AbstractChannel:Channel基本的实现,在其中有几个重要的属性:

- unsafe:非jdk中的unsafe,这是netty定义的unsafe,其中定义了bind,connect等方法,在实际和网络打交道的时候会使用到
- DefaultChannelPipeline:ChannelPipeline的默认实现,这就是Channel分配的ChannelPipline
- EventLoop:此Channel注册到的EventLoop
AbstractNioChannel:使用Selector进行IO多路复用的Channel,其中有两个重要的属性

从此可与看出,Netty中的Nio Channel 和 jdk中Channel的关系,它们是一对一的
AbstractNioChannel拥有NIO的Channel,具备NIO的注册、连接等功能。但IO的读写交给了其子类。
AbstractNioByteChannel&AbstractNioMessageChannel
AbstractNioByteChannel是面向字节的,通常是客户端进行使用,AbstractNioMessageChannel面向消息,通过是服务端进行使用。为什么netty这样设计昵?
通常情况下,客户端只需要发送消息,因此直接将消息内容转换为字节流进行输出即可,这时候使用AbstractNioByteChannel更为合适。 服务端在接收到客户端的消息后,需要对消息进行解码、反序列化、处理等操作,这时候使用AbstractNioMessageChannel更为合适。因为AbstractNioMessageChannel提供了方便的消息处理功能,可以对收到的字节流进行解码、转换成特定的消息对象,并提供了方便的事件驱动机制,方便开发者对消息进行处理和管理。 例如,在自定义的RPC协议中,服务端需要解码和反序列化请求消息,调用相应的服务方法,并将响应消息转换为字节流输出。使用AbstractNioMessageChannel可以方便地处理这些消息读写操作,使用自定义的解码器和编码器可以将字节流转换为特定的消息对象,并且在ChannelHandler中实现业务逻辑,通过事件驱动机制方便地管理消息的读写和处理。 综上所述,AbstractNioByteChannel适合用于处理字节流数据,适合用于客户端。而AbstractNioMessageChannel适合用于处理消息,适合用于服务端
NioSocketChannel
NioSocketChannel是AbstractNioByteChannel的子类,NioSocketChannel封装了NIO中的SocketChannel,实现了IO的读写连接操作。Netty服务的每个连接都会生成一个NioSocketChannel对象。
NioServerSocketChannel
NioServerSocektChannel是AbstractNioMessageChannel的子类,一般是服务端进行使用,并且只负责监听Socket的接入,不关心IO读写。
二丶服务端启动是NioServerSocketChannel是如何实例化并注册到EventLoop中的
客户端的NioSocketChannel实例化和注册流程与服务端类似

如上代码中,服务端指定了Channel类型,然后调用bind方法,在bind方法中会进行Channel的初始化+注册到EventLoop中

1.NioServerSocketChannel的实例化
在不指定ChannelFactory的时候,这里默认是ReflectiveChannelFactory,如同其名称一样,ReflectiveChannelFactory会使用反射的方式构建出Channel

随后会使用SelectorProvider#openServerSocketChannel创建出一个jdk原生的ServerSocketChannel。
然后调用父类构造器,设置Channel为非阻塞,并调用newUnsafe和newChannelPipeline实例化unsafe和channelPipeline

对于服务端来说这里newUnsafe产生的是NioMessageUnsafe,ChannelPipeline通常使用的是DefaultChannelPipeline
2.NioServerSocketChannel的初始化

初始化会将我们在ServerBootStrap中设置的参数设置到NioServerSocketChannel中
并向ChannelPipeline添加一个ServerBootstrapAcceptor,ServerBootstrapAcceptor和Netty的reactor模式有关,此类的作用后续进行学习。
3.NioServerSocketChannel的注册
随后会使用ServerBootStrap中的EventLoopGroup#register方法进行注册,这里的使用的EventLoopGroup是demo中指定的bossGroup

因为在Reactor模式中,bossGroup负责处理ACCEPT事件,单线程使用Selector监听多路IO的Accept事件,然后将这些套接字交给上面的WorkerGroup,so这里NioServerSocketChannel注册到bossGroup中
bossGroup并不会自己进行注册,而是使用next方法找到自己的小弟——EventLoop,进行注册

这里会使用到EventExecutorChooser进行选择EventLoop,Netty自带两种策略
如果EventLoopGroup中的EventLoop是2的幂次个,那么使用PowerOfTwoEventExecutorChooser

它使用取模在众多EventLoop中选择一个
如果EventLoopGroup中的EventLoop不是2的幂次个,那么使用GenericEventExecutorChooser,直接对EventLoop个数进行取模
选择完EventLoop后,会调用EventLoop的注册方法,最终会使用AbstractUnsafe#register,其中会先判断执行的线程是不是EventLoop线程,如果不是那么会将任务提交到EventLoop中执行,源码如下:

注册的即将当前Channel注册到Selector,并且attachment指定为当前Channel,这样NioEventLoop在进行IO多路复用的的时候,可通过attachment方法拿到当前Channel

注册结束后会使用ChannelPipeline触发channelRegistered事件,关于ChannelPipeline下一篇博客中进行学习。
在这一步,还会触发NioEventLoop线程的启动,进行事件循环,在一个死循环中使用Selector监听这个Channel的IO事件,并处理其他调度任务,异步任务。(如何启动NioEventLoop线程的——Netty源码学习2——NioEventLoop的执行#NioEventLoop的启动)
三丶ChannelPipeline

上面我们说到一个Channel的实例化会触发ChannelPipeline的实例化。ChannelPipeline 和 ChannelHandler 也是我们在平时应用开发的过程中打交道最多的组件,通常程序员使用Netty进行开发只需要将自己定义的ChannelHandler加入到ChannelPipeline中。
ChannelPipeline即是ChannelHandler的流水线,ChannelPipeline 可以看作是 ChannelHandler 的容器载体,它是由一组 ChannelHandler 实例组成的,内部通过双向链表将不同的 ChannelHandler 链接在一起,如下图所示。当有 I/O 读写事件触发时,ChannelPipeline 会依次调用 ChannelHandler 列表对 Channel 的数据进行拦截和处理。

1.ChannelHandlerContext
ChannelHandlerContext 用于保存 ChannelHandler 上下文,其包含了 ChannelHandler 生命周期的所有事件,如 connect、bind、read、flush、write、close 等。
2.ChannelInboundHandler 和 ChannelOutboundHandler
在客户端与服务端通信的过程中,数据从客户端发向服务端的过程叫出站,反之称为入站。数据先由一系列 InboundHandler 处理后入站,然后再由相反方向的 OutboundHandler 处理完成后出站。
3.DefaultChannelPipeline

DefaultChannelPipeline是netty中ChannelPipeline的默认实现,内部保存了HeadContext,和TailContext分别作为链表的头和尾

可以看到HeadContext即是ChannelOutboundInvoker(出站处理器)也是ChannelInboundInvoker(出站处理器),这是因为网络数据写入操作的入口就是由 HeadContext 节点完成的。HeadContext 作为 Pipeline 的头结点负责读取数据并开始传递 入站事件,当数据处理完成后,数据会反方向经过各个 ChannelOutboundInvoker的处理,最终传递到 HeadContext。
而TailContext只实现了ChannelInboundInvoker,它是最后一个ChannelInboundInvoker,用于结束入站事件的传播。
4.ChannelInboundHandler&ChannelOutboundHandler
二者都是ChannelHandler的子接口,其方法的声明对于了Netty中对事件的抽象

4.1 ChannelInboundHandler
| 方法名&事件 | |
|---|---|
| channelRegistered | 当Channel注册到它的EventLoop并且能够处理I/O时调用 |
| channelUnregistered | 当Channel从它的EventLoop中注销并且无法处理任何I/O时调用 |
| channelActive | 当Channel处理于活动状态时被调用 |
| channelInactive | 不再是活动状态且不再连接它的远程节点时被调用 |
| channelReadComplete | 当Channel上的一个读操作完成时被调 |
| channelRead | 当从Channel读取数据时被调用 |
| channelWritabilityChanged | 当Channel的可写状态发生改变时被调用 |
| userEventTriggered | 当ChannelInboundHandler.fireUserEventTriggered()方法被调用时触发 |
4.2 ChannelOutBoundHandler
| 方法名&事件 | |
|---|---|
| bind | 当请求将Channel绑定到本地地址时被调用 |
| connet | 当请求将Channel连接到远程节点时被调用 |
| disconnect | 当请求将Channel从远程节点断开时调用 |
| close | 当请求关闭Channel时调用 |
| deregister | 当请求将Channel从它的EventLoop注销时调用 |
| read | 当请求从Channel中读取数据时调用 |
| flush | 当请求通过Channel将入队数据冲刷到远程节点时调用 |
| write | 当请求通过Channel将数据写入远程节点时被调用 |
四丶总结
此篇初探了Channel,ChannelPipeline,ChannelContext,ChannelHandler之间的关系,深入学习了Netty中的Nio Channel是怎么和jdk中的Channel组织起来的。
上面我们说到在Netty中,NioEventLoop是事件的调度中心,它控制了Io事件和其他任务的调度,但是io事件的处理是依赖ChannelHandler的,多个ChannelHandler又由ChannelPipline组装成流水线依次执行
那么一个网络请求在Netty中是怎么从NioEventLoop事件循环中交由ChannelPipline进行事件传播与处理的昵?这个下篇中进行学习和总结。
Netty源码学习3——Channel ,ChannelHandler,ChannelPipeline的更多相关文章
- Netty源码学习(六)ChannelPipeline
0. ChannelPipeline简介 ChannelPipeline = Channel + Pipeline,也就是说首先它与Channel绑定,然后它是起到类似于管道的作用:字节流在Chann ...
- 【Netty源码学习】ChannelPipeline(一)
ChannelPipeline类似于一个管道,管道中存放的是一系列对读取数据进行业务操作的ChannelHandler. 1.ChannelPipeline的结构图: 在之前的博客[Netty源码学习 ...
- Netty源码学习系列之4-ServerBootstrap的bind方法
前言 今天研究ServerBootstrap的bind方法,该方法可以说是netty的重中之重.核心中的核心.前两节的NioEventLoopGroup和ServerBootstrap的初始化就是为b ...
- 【Netty源码学习】DefaultChannelPipeline(三)
上一篇博客中[Netty源码学习]ChannelPipeline(二)我们介绍了接口ChannelPipeline的提供的方法,接下来我们分析一下其实现类DefaultChannelPipeline具 ...
- Netty 源码学习——EventLoop
Netty 源码学习--EventLoop 在前面 Netty 源码学习--客户端流程分析中我们已经知道了一个 EventLoop 大概的流程,这一章我们来详细的看一看. NioEventLoopGr ...
- Netty 源码学习——客户端流程分析
Netty 源码学习--客户端流程分析 友情提醒: 需要观看者具备一些 NIO 的知识,否则看起来有的地方可能会不明白. 使用版本依赖 <dependency> <groupId&g ...
- 【Netty源码学习】ServerBootStrap
上一篇博客[Netty源码学习]BootStrap中我们介绍了客户端使用的启动服务,接下来我们介绍一下服务端使用的启动服务. 总体来说ServerBootStrap有两个主要功能: (1)调用父类Ab ...
- netty源码学习
概述 Netty is an asynchronous event-driven network application framework for rapid development of main ...
- Netty源码分析--创建Channel(三)
恩~,没错,其实这一篇才是真正的开始分析源码,你打我呀~. 先看一下我Netty的启动类 private void start() throws Exception { EventLoopGroup ...
- 【Netty源码学习】EventLoopGroup
在上一篇博客[Netty源码解析]入门示例中我们介绍了一个Netty入门的示例代码,接下来的博客我们会分析一下整个demo工程运行过程的运行机制. 无论在Netty应用的客户端还是服务端都首先会初始化 ...
随机推荐
- 2013年蓝桥杯C/C++大学B组省赛真题(翻硬币)
题目描述: 明正在玩一个"翻硬币"的游戏. 桌上放着排成一排的若干硬币.我们用 * 表示正面,用 o 表示反面(是小写字母,不是零). 比如,可能情形是:**oo***oooo 如 ...
- Android failed linking file resources.
今天在配置一个app 的启动资源文件时老是提示error: failed linking file resources. 且转悠半天这个提示太难定位错误的范围了,最后发现旁边的一个图标,鼠标移入tit ...
- Tomcat请求处理流程与源码浅析
系列文章目录和关于我 一丶Connector 在tomcat中,Connector负责开启socket并且监听客户端请求,返回响应数据. 其中: Endpoint:tomcat中没有这个接口,只有Ab ...
- 2023最新IntellJ IDEA诺依SpringCloud开发部署文档(保姆级别)
目录 若依RuoYi v3.6.2部署文档 一.环境构建 二.模块描述 三.部署后端 1.下载到本地. 2.MySQL导入数据. 3.Nacos修改 (1)保证本地Nacos下载安装成功,修改本地Na ...
- 文件系统考古2:1984 - BSD Fast Filing System
今天继续与大家分享系列文章<50 years in filesystems>,由 KRISTIAN KÖHNTOPP 撰写. 我们将进入文件系统的第二个十年,即1984年,计算机由微型计算 ...
- 20230611 再次升级SSD
家里常用电脑的硬盘又显得捉襟见肘,老规矩,升级SSD.幸亏几年前摸索的方法记录下来了,翻出以前的博客复习一下.为了保险起见,也重新在网上搜了一下,看是不是有新的更方便的方法,答案是没有,只是搜出很多推 ...
- asp.net程序通过Microsoft Azure令牌授予流获取UserInfo终结点实现单点登录--隐式授予流(OIDC协议)
1. Microsoft Azure令牌授予流 令牌授予流种类如下: 本章节采用: 隐式授予流: 2. 隐式授予流的实现 流程:重定向到authorize--->拿到access_token-- ...
- Unity iOS Guideline 1.3 - Safety - Kids Category 被拒
解决办法: 不使用unity 的分析SDK //关闭unity信息收集服务 UnityEngine.Analytics.Analytics.enabled = false; UnityEngine.A ...
- .NET Core 3.1使用docker打包并部署
目录 简介 环境介绍 开发环境 部署环境 编写Dockerfile文件 生成Docker镜像 运行容器 访问接口 结语 简介 本文主要说明使用.NET Core 3.1搭建的站点如何使用docker打 ...
- 加速LakeHouse ACID Upsert的新写时复制方案
概述 随着存储表格式 Apache Hudi.Apache Iceberg 和 Delta Lake 的发展,越来越多的公司正在这些格式的基础上构建其 Lakehouse,以用于许多用例,例如增量摄取 ...