Netty学习——源码篇6 Pipeline设计原理

1 Pipeline设计原理

        在Netty中每个Channel都有且仅有一个ChannelPipeline与之对应,它们的组成关系如下图:

        通过上图可以看到,一个Channel包含了一个ChannelPipeline,而ChannelPipeline中又维护了一个由ChannelHandlerContext组成的双向链表。这个链表的头是HeadContext,链表的尾是TailContext,并且每个ChannelHandlerContext又关联着一个ChannelHandler。

        通过分析代码,已经知道了一个Channel初始化的基本过程,下面在回顾一下。AbstractChannel构造器的代码如下:

    protected AbstractChannel(Channel parent) {this.parent = parent;id = newId();unsafe = newUnsafe();pipeline = newChannelPipeline();}

         AbstractChannel有一个pipeline属性,在构造器中会把它初始化为DefaultChannelPipeline的实例。这里的代码就印证了这一点:每个Channel都有一个ChannelPipeline。来看一下DefaultChannelPipeline的构造器,代码如下:

    protected DefaultChannelPipeline(Channel channel) {this.channel = ObjectUtil.checkNotNull(channel, "channel");succeededFuture = new SucceededChannelFuture(channel, null);voidPromise =  new VoidChannelPromise(channel, true);tail = new TailContext(this);head = new HeadContext(this);head.next = tail;tail.prev = head;}

        在DefaultChannelPipeline构造器中,首先将与之关联的Channel保存到属性channel中。然后实例化两个ChannelHandlerContext:一个是HeadContext实例Head,另一个是TailContext实例Tail。接着将Head和Tail互相指向,构成一个双向链表。

        特别注意的是:在开始的示意图中,Head和Tail并没有包含ChannelHandler,这是因为HeadContext和TailContext继承于AbstractChannelHandlerContext的同时,也实现了ChannelHandler接口,所以它们有Context和Handler的双重属性。

2 ChannelPipeline初始化

        下面看一下ChannelPipeline的初始化具体做了哪些工作。先回顾一下,在实例化一个Channel时,会伴随着一个ChannelPipeline的实例化,并且此Channel会与这个ChannelPipeline相互关联,这一点可以通过NioEventLoop的父类AbstractChannel的构造器予以佐证,代码如下:

    protected AbstractChannel(Channel parent) {this.parent = parent;id = newId();unsafe = newUnsafe();pipeline = newChannelPipeline();}

        当实例化一个NioSocketChannel时,其pipeline属性就是新创建的DefaultChannelPipeline对象,再来回顾一下DefaultChannelPipeline的构造方法,代码如下:

    protected DefaultChannelPipeline(Channel channel) {this.channel = ObjectUtil.checkNotNull(channel, "channel");succeededFuture = new SucceededChannelFuture(channel, null);voidPromise =  new VoidChannelPromise(channel, true);tail = new TailContext(this);head = new HeadContext(this);head.next = tail;tail.prev = head;}

        上面代码中的Head实现了ChannelInboundHandler接口,而Tail实现了ChannelOutboundHandler接口,因此可以说Head和Tail就是ChannelHandler,又是ChannelHandlerContext。

3 ChannelInitializer的添加

        前面分析过Channel的组成,最开始的时候ChannelPipeline中含有两个ChannelHandlerContext,但是此时的Pipeline并不能实现特定的功能,因为还没有添加自定义的ChannelHandler。通常来说,在初始化Bootstrap时,会添加自定义的ChannelHandler,下面就以具体的客户端启动代码片段举例:

Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.SO_KEEPALIVE,true).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline pipeline = socketChannel.pipeline();pipeline.addLast(new StringDecoder());pipeline.addLast(new StringEncoder());System.out.println("初始化channel:" + socketChannel);}});

        在调用Handler时,传入ChannelInitializer对象,它提供了一个initChannel方法来初始化ChannelHandler。通过代码跟踪,发现ChannelInitializer是在Bootstrap的init方法中添加到ChannelPipiline中的,代码如下:

    void init(Channel channel) throws Exception {ChannelPipeline p = channel.pipeline();p.addLast(new ChannelHandler[]{this.config.handler()});Map<ChannelOption<?>, Object> options = this.options0();synchronized(options) {Iterator i$ = options.entrySet().iterator();while(true) {if (!i$.hasNext()) {break;}Entry e = (Entry)i$.next();try {if (!channel.config().setOption((ChannelOption)e.getKey(), e.getValue())) {logger.warn("Unknown channel option: " + e);}} catch (Throwable var10) {logger.warn("Failed to set a channel option: " + channel, var10);}}}Map<AttributeKey<?>, Object> attrs = this.attrs0();synchronized(attrs) {Iterator i$ = attrs.entrySet().iterator();while(i$.hasNext()) {Entry<AttributeKey<?>, Object> e = (Entry)i$.next();channel.attr((AttributeKey)e.getKey()).set(e.getValue());}}}

        从上面的代码可见,将handler()方法返回的ChannelHandler添加到Pipeline中,而handler()方法返回的其实就是在初始化Bootstrap时通过handler方法设置的ChannelInitializer实例,因此这里就将ChannelInitializer插到了Pipieline的末端。此时Pipeline的结构如下图所示:

        这是会有一个疑问,明明插入的是 ChannelInitializer实例,为什么在ChannelPipeline的双向链表中的元素却是一个ChannelHandlerContext呢?继续看源码,在Bootstrap的init方法中会调用p.addList()方法,将ChannelInitializer插入链表的末端,代码如下:

    @Overridepublic final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {final AbstractChannelHandlerContext newCtx;synchronized (this) {checkMultiplicity(handler);newCtx = newContext(group, filterName(name, handler), handler);addLast0(newCtx);// If the registered is false it means that the channel was not registered on an eventloop yet.// In this case we add the context to the pipeline and add a task that will call// ChannelHandler.handlerAdded(...) once the channel is registered.if (!registered) {newCtx.setAddPending();callHandlerCallbackLater(newCtx, true);return this;}EventExecutor executor = newCtx.executor();if (!executor.inEventLoop()) {newCtx.setAddPending();executor.execute(new Runnable() {@Overridepublic void run() {callHandlerAdded0(newCtx);}});return this;}}callHandlerAdded0(newCtx);return this;}

        addList方法有很多重载的方法,只需要关注这个方法即可。上面的addList方法中,首先检查ChannelHandler的名字是否重复,如果不重复,则调用newContext方法为这个Handler创建一个对应的DefaultChannelHandlerContext实例,并与之关联起来。

        为了添加一个Handler到Pipeline中,必须把此Handler包装成ChannelHandlerContext。因此在上面的代码中,我们新实例化一个newCx对象,并将Handler作为参数传递到构造方法中。下面来看一下DefaultChannelHandlerContext的构造器。

    DefaultChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {super(pipeline, executor, name, isInbound(handler), isOutbound(handler));if (handler == null) {throw new NullPointerException("handler");}this.handler = handler;}

        在DefaultChannelHandlerContext的构造器中,调用了isInbound()方法和isOutbound()方法,这两个方法的代码如下:

    private static boolean isInbound(ChannelHandler handler) {return handler instanceof ChannelInboundHandler;}private static boolean isOutbound(ChannelHandler handler) {return handler instanceof ChannelOutboundHandler;}

        从上面代码中可以看到,当一个Handler实现了ChannelInboundHandler接口,则isInbound返回true;类似的,当一个Handler实现了ChannelOutboundHandler接口,则isOuntbound返回true。而这两个boolean类型变量会传递给父类AbstractChannelHandlerContext中,并初始化父类的两个属性:inbound和outbound。

        这里的ChannelInitializer所对应的DefaultChannelHandlerContext的inbound与outbound属性分别是什么呢?先来看ChannelInitializer的类层次结构图,如下图:

        可以看到,ChannelInitializer仅仅实现了ChannelInboundHandler接口,因此这里实例化的DefaultChannelHandlerContext的inbound是true,outbound是false。

        inbound和outbound这两个属性关系到Pipeline事件的流向与分类,因此十分关键。这里先记住一个结论:ChannelInitializer所对应的DefaultChannelHandlerContext的inbound=true,outbound=false。

        当创建好Context之后,就将这个Context插入Pipeline的双向链表中。

DefaultChannelPipeline.java
    private void addLast0(AbstractChannelHandlerContext newCtx) {AbstractChannelHandlerContext prev = tail.prev;newCtx.prev = prev;newCtx.next = tail;prev.next = newCtx;tail.prev = newCtx;}

        添加完ChannelInitializer的Pipeline内部如下图所示

4 自定义ChannelHandler的添加过程

        上面分析了ChannelInitializer是如何插入Pipeline中的,接下来探讨ChannelInitializer在哪里被调用、ChannelInitializer的作用以及自定义的ChannelHandler是如何插入Pipeline中的。

        自定义ChannelHandler的添加过程,发生在AbstractUnsafe的register方法中,在这个方法中调用了pipeline.fireChannelRegister()方法,代码如下:

    @Overridepublic final ChannelPipeline fireChannelRegistered() {AbstractChannelHandlerContext.invokeChannelRegistered(head);return this;}

        再看AbstractChannelHandlerContext的invokeChannelRegister()方法。

    static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {EventExecutor executor = next.executor();if (executor.inEventLoop()) {next.invokeChannelRegistered();} else {executor.execute(new Runnable() {@Overridepublic void run() {next.invokeChannelRegistered();}});}}

        很显然,这个代码将从Head开始遍历Pipeline的双向链表,然后找到第一个属性inbound为true的ChannelHandlerContext实例。在分析ChannelInitializer时,专门分析了inbound和outbound属性,现在这里就用上了。回想一下,ChannelInitializer实现了ChannelInboundHandler,因此它所对应的ChannelHandlerContext的inbound属性为true,因此这里返回的就是ChannelInitializer实例所对应的ChannelHandlerContext对象,如下图所示

        当获取inbound的Context后,就调用它的invokeChannelRegistered()方法。

    private void invokeChannelRegistered() {if (invokeHandler()) {try {((ChannelInboundHandler) handler()).channelRegistered(this);} catch (Throwable t) {notifyHandlerException(t);}} else {fireChannelRegistered();}}

         我们已经知道,每个ChannelHandler都和一个ChannelHandlerContext关联,可以通过ChannelHandlerContext获取对应的ChannelHandler。很明显,这里handler()方法返回的对象其实就是一开始实例化的ChannelInitializer对象,接着调用了ChannelInitializer的channelRegister()方法。ChannelInitializer的channelRegister()方法的代码如下:

    public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {if (this.initChannel(ctx)) {ctx.pipeline().fireChannelRegistered();} else {ctx.fireChannelRegistered();}}private boolean initChannel(ChannelHandlerContext ctx) throws Exception {if (this.initMap.putIfAbsent(ctx, Boolean.TRUE) == null) {try {this.initChannel(ctx.channel());} catch (Throwable var6) {this.exceptionCaught(ctx, var6);} finally {this.remove(ctx);}return true;} else {return false;}

        initChannel()方法就是在初始化Bootstrap时,调用handler方法传入的匿名内部类所实现的方法。因此,在调用这个方法之后,自定义的ChannelHandler就插入到Pipeline中,此时Pipeline的状态如下图:

        当添加完自定义的ChannelHandler后,在finally代码块会删除自定义的ChannelHandler,也就是remove(ctx),最终调用ctx.pipeline().remove(this),因此最后Pipeline的状态如下图:

        到此,自定义ChannelHandler的添加过程也就分析完成了。 

5 给ChannelHandler命名

        pipeline.addXXX()都有一个重载方法,例如addList()有一个重载的版本,代码如下:

public final ChannelPipeline addLast(String name, ChannelHandler handler) {return addLast(null, name, handler);}

        第一个参数指定添加的是Handler的名字(更准确的说是ChannelHandlerContext的名字)。那么Handler的名字有什么用呢?如果不设置name,那么Handler默认的名字是怎么样的?上面的方法会调用重载的addLast()方法,代码如下:

    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {final AbstractChannelHandlerContext newCtx;synchronized (this) {checkMultiplicity(handler);newCtx = newContext(group, filterName(name, handler), handler);addLast0(newCtx);// If the registered is false it means that the channel was not registered on an eventloop yet.// In this case we add the context to the pipeline and add a task that will call// ChannelHandler.handlerAdded(...) once the channel is registered.if (!registered) {newCtx.setAddPending();callHandlerCallbackLater(newCtx, true);return this;}EventExecutor executor = newCtx.executor();if (!executor.inEventLoop()) {newCtx.setAddPending();executor.execute(new Runnable() {@Overridepublic void run() {callHandlerAdded0(newCtx);}});return this;}}callHandlerAdded0(newCtx);return this;}

        第一个参数设置为null,不用关心。第二个参数就是Handler的名字。有代码可知,再添加一个Handler之前,需要调用checkMultiplicity()方法来确定新添加的Handler名字是否与已添加的Handler名字重复。

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

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

相关文章

【笔记】深入理解JVM机制

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 JVM 运⾏流程图 JVM 中内存区域划分 方法区 / 元数据区 堆 栈 程序计数器 本地方法栈 内存区域总结 JVM 中类加载过程 …

C++ STL- list 的使用以及练习

目录 0.引言 1. list 介绍 2. list 使用 2.1 构造函数 2.2 list iterator 的使用 3 list capacity 4. list element access 5. list modifiers 6. list 迭代器失效 7. list 与vector 对vector 8. OJ 题讲解 删除链表的倒数第 N 个节点&#xff1a; 0.引言 …

Jetson AGX ORIN 配置 FGVC-PIM 神经网络(包含 arm64 下面 torch 和 torchvision 配置内容)

Jetson AGX ORIN 配置 FGVC-PIM 神经网络 文章目录 Jetson AGX ORIN 配置 FGVC-PIM 神经网络配置 ORIN 环境创建 FGVC-PIM 虚拟环境安装 PyTorch安装 torchvision安装其他依赖包 配置 ORIN 环境 首先先配置 ORIN 的环境&#xff0c;可以参考这个链接&#xff1a; Jetson AGX …

Go第三方框架--gin框架(一)

序言 Gin框架作为go语言使用最多的web框架&#xff0c;以其快速的响应速度和对复杂http路由配置的支持受到程序员和媛们的喜爱&#xff0c;几乎统治了web市场。但作为一名合格的程序员&#xff0c;要知其然更要知其所以然&#xff0c;不然八股文背的也没有啥意思。本着这个原则…

JavaScript中的Lexical Environment

概要 本文主要介绍JavaScript中的一个重要概念Lexical Environment&#xff0c;它可以帮助我们解释我们为什么可以通过嵌套方法&#xff0c;共享数据&#xff0c;以及为什么可以在函数中定义一个和全局变量同名的变量&#xff0c;并且不会影响到全局变量。 基本分析 基本概念…

如何使用Python进行网络安全与密码学【第149篇—密码学】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 用Python进行网络安全与密码学&#xff1a;技术实践指南 随着互联网的普及&#xff0c;网络…

几个常用的AI工具

人工智能大模型的出现对人类社会产生了深远的影响&#xff0c;这些影响既包括积极的方面&#xff0c;也包括一些潜在的挑战: 1. **提高效率**&#xff1a;AI大模型能够快速处理大量数据&#xff0c;提高工作效率&#xff0c;尤其在数据分析、自然语言处理等领域。 2. **辅助决…

面向对象【枚举类】

文章目录 枚举类定义枚举类enum 方式定义的要求和特点 enum 中常用方法实现接口的枚举类 枚举类 枚举类是一种特殊的类&#xff0c;它用于定义一组固定数量的常量。枚举类在实际开发中非常有用&#xff0c;因为它们可以增加代码的可读性和可维护性。本文将介绍Java枚举类的定义…

Java基于微信小程序的校园请假系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#…

【Java程序设计】【C00370】基于(JavaWeb)Springboot的公司进存销管理系统(有论文)

TOC 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;博客中有上百套程序可供参考&#xff0c;欢迎共同交流学习。 项目简介 项目获取 &#x1f345;文末点击卡片…

视频记录历史播放位置效果

简介 每次打开页面视频从上一次的播放位置开始播放 利用lodash库做节流 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sca…

UI界面设计是什么?一篇文章带你全面了解

伴随着因特网的飞速发展&#xff0c;很多与因特网相关的职位都衍生出来了&#xff0c;UI 界面设计师是因特网的核心职位之一。UI 界面设计已经渗透到我们生活的各个方面&#xff0c;包括网站、应用程序或其它数字平台上的按钮、菜单布局、配色方案和排版等。很多人认为 UI 界面…

具有徊滞特性的欠压锁定功能的B3842/43/44是专为脱线和Dc-Dc开关电源应用设计的

B3842/43/44是专为脱线和Dc-Dc开关电源应用设计的恒频电流型Pwd控制器内部包含温度补偿精密基准、供精密占空比调节用的可调振荡器、高增益混放大器、电流传感比较器和适合作功率MOST驱动用的大电流推挽输出颇以及单周期徊滞式限流欠压锁定、死区可调、单脉冲计数拴锁等保护电路…

【Java程序设计】【C00369】基于(JavaWeb)Springboot的笔记记录分享平台(有论文)

[TOC]() 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;博客中有上百套程序可供参考&#xff0c;欢迎共同交流学习。 项目简介 项目获取 &#x1f345;文末点击…

BSV区块链的应用开发前景——通过标准化来促进创新

​​发表时间&#xff1a;2024年3月5日 近年来区块链领域的发展日新月异&#xff0c;各种全新的技术和方法论正在迅猛涌现。在这个瞬息万变的环境之中&#xff0c;标准化不仅仅会为开发者们带来便利&#xff0c;同时也促进了应用之间的互操作性&#xff0c;并且推动着生态系统的…

SAP ABAP-BOPF基础训练-01简介与架构

1. 介绍-Introduction ① BOPF是什么&#xff1f;BOPF(the Business Object Processing Framework)&#xff1a;业务对象处理框架 提供了一种增量和模块化的方法&#xff0c;以符合企业面向服务体系结构(eSOA)的方式实现业务对象&#xff1b; 部分平台基础层&#xff0c;软件组…

UI设计师必备软件:2024年趋势解读!

设计的两个关键方面是用户界面 (UI) 和用户体验 (UX)&#xff0c;UI设计侧重于人们如何与产品互动的审美元素&#xff0c;UX设计更侧重于人们如何使用产品&#xff0c;无论你的重点是什么 UX 还是 UI&#xff0c;或者你是否试图将两者结合起来&#xff0c;你需要高质量的UI设计…

【C++】share_ptr详解

一、share_ptr 的简单使用 1.1、基本用法 从较浅的层面看&#xff0c;智能指针是利用了一种叫做RAII&#xff08;资源获取即初始化&#xff09;的技术对普通的指针进行封装&#xff0c;这使得智能指针实质是一个对象&#xff0c;行为表现的却像一个指针。 智能指针的作用是防…

【MySQL数据库】数据类型和简单的增删改查

目录 数据库 MySQL的常用数据类型 1.数值类型&#xff1a; 2.字符串类型 3.日期类型 MySQL简单的增删改查 1.插入数据&#xff1a; 2.查询数据&#xff1a; 3.修改语句&#xff1a; 4.删除语句&#xff1a; 数据库 平时我们使用的操作系统都把数据存储在文件中&#…

深入了解服务器硬件:从基础知识到实际应用

在当今数字化的社会中&#xff0c;服务器扮演着至关重要的角色&#xff0c;它们是支撑互联网、云计算、大数据等技术发展的基石。而理解服务器硬件的基础知识对于从事IT领域的人员来说至关重要。本文将从服务器硬件的基础知识出发&#xff0c;介绍服务器硬件的组成、作用及其在…