【图解IO与Netty系列】Netty源码解析——ChannelPipeline中的责任链模式

news2024/10/7 16:26:00

Netty源码解析——ChannelPipeline中的责任链模式

  • ChannelPipeline的作用
  • ChannelPipeline的设计
  • ChannelPipeline源码解析

ChannelPipeline的作用

ChannelPipeline在Netty中的作用,主要是在有事件就绪时,用于处理就绪事件的。我们知道真正处理就绪事件的其实是ChannelHandler,但是由于ChannelHandler有多个,可能会同时处理这个就绪事件,于是Netty就设计了一个ChannelPipeline,利用责任链模式串联起多个ChannelHandler。

在这里插入图片描述

ChannelPipeline的设计

ChannelPipeline是责任链模式的一种实现,ChannelPipeline里面是一个由多个ChannelHandlerContext串联起来的双向链表,每个ChannelHandlerContext有一个后继指针next和前驱指针prev用于指向后面一个和前面一个ChannelHandlerContext,而ChannelHandler则是被包装在ChannelHandlerContext中。

在这里插入图片描述

之所以不让ChannelHandler维护next和prev指针,是尽量保证ChannelHandler的职责单一(处理就绪事件)。

在这里插入图片描述

在ChannelPipeline中,入站事件的处理顺序是从head到tail方向依次调用ChannelHandler,而处理出站事件则是从tail到head方向依次调用ChannelHandler。并且只有ChannelInboundHandler类型的ChannelHandler才会处理入站事件,只有ChannelOutboundHandler类型的ChannelHandler才会处理出站事件。

在这里插入图片描述

ChannelPipeline源码解析

我们就以read事件的处理为例,看一下ChannelPipeline的源码设计。ChannelPipeline的fireChannelRead()方法就是触发ChannelPipeline处理read事件的入口,我们从这里开始。

io.netty.channel.DefaultChannelPipeline#fireChannelRead

    @Override
    public final ChannelPipeline fireChannelRead(Object msg) {
        AbstractChannelHandlerContext.invokeChannelRead(head, msg);
        return this;
    }

调用AbstractChannelHandlerContext的静态方法invokeChannelRead(),参数head就是HeadContext,是ChannelPipeline中的链表的头部节点。

在这里插入图片描述

io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)

    static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
        final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRead(m);
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRead(m);
                }
            });
        }
    }

由于是在NioEventLoop中的事件循环中监听到事件就绪触发的事件处理,所以if (executor.inEventLoop())条件一般都是满足的,因此直接调用next.invokeChannelRead(m)方法处理。

在这里插入图片描述

io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)

    private void invokeChannelRead(Object msg) {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRead(this, msg);
            } catch (Throwable t) {
                invokeExceptionCaught(t);
            }
        } else {
            fireChannelRead(msg);
        }
    }

AbstractChannelHandlerContext的invokeChannelRead方法里面,一般会进入if条件分支,拿到ChannelHandlerContext的ChannelHandler,因为是处理read事件,read事件是入站事件,所以是ChannelInboundHandler类型,然后调用ChannelInboundHandler的channelRead方法进行事件处理,ChannelInboundHandler的channelRead方法就是我们实现的处理逻辑。

在这里插入图片描述

当我们在ChannelInboundHandler的channelRead方法处理完毕后,调用ChannelHandlerContext的fireChannelRead(msg)方法,read事件就会继续往下传递,否则事件处理就在当前的ChannelInboundHandler终止。

这里HeadContext的ChannelHandler就是HeadContext自己,channelRead方法就是直接调用ctx.fireChannelRead(msg)。

io.netty.channel.DefaultChannelPipeline.HeadContext#channelRead

        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            ctx.fireChannelRead(msg);
        }

我们再看一个Netty内置的ChannelInboundHandler,比如LoggingHandler。

io.netty.handler.logging.LoggingHandler#channelRead

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (logger.isEnabled(internalLevel)) {
            logger.log(internalLevel, format(ctx, "READ", msg));
        }
        ctx.fireChannelRead(msg);
    }

打印一行日志,然后调用ctx.fireChannelRead(msg)让事件继续往后传递,如果它不调用ctx.fireChannelRead(msg),那么事件处理就到此为止了,这也是在开发Netty应用时需要注意的地方。

在这里插入图片描述

ctx.fireChannelRead(msg)会进入AbstractChannelHandlerContext的fireChannelRead方法。

io.netty.channel.AbstractChannelHandlerContext#fireChannelRead

    @Override
    public ChannelHandlerContext fireChannelRead(final Object msg) {
        invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
        return this;
    }

findContextInbound(MASK_CHANNEL_READ)是找到下一个ChannelInboundHandler所在的ChannelHandlerContext,里面是通过位运算进行匹配的,优点小复杂,我们就不看了。假设现在找到了下一个ChannelHandlerContext,那么进入invokeChannelRead方法。

在这里插入图片描述

io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)

    static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
        final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRead(m);
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRead(m);
                }
            });
        }
    }

可以看到,又回到了AbstractChannelHandlerContext的invokeChannelRead方法,这个方法我们上面已经看过了,这样整个链式调用就串联起来了。

在这里插入图片描述

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

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

相关文章

力扣SQL50 每月交易 I 求和 SUM(条件表达式) DATE_FORMAT(日期,指定日期格式)

Problem: 1193. 每月交易 I 👨‍🏫 参考题解 Code select DATE_FORMAT(trans_date, %Y-%m) AS month,country,count(*) as trans_count,count(if(state approved, 1, NULL)) as approved_count,sum(amount) as trans_total_amount,sum(if(state appr…

5.3 Python len()函数:获取字符串长度或字节数

Python len()函数详解:获取字符串长度或字节数 Python 中,要想知道一个字符串有多少个字符(获得字符串长度),或者一个字符串占用多少个字节,可以使用 len 函数。 len 函数的基本语法格式为: …

性能工具之 MySQL OLTP Sysbench BenchMark 测试示例

文章目录 一、前言二、测试环境1、服务器配置2、测试拓扑 三、测试工具安装四、测试步骤1、导入数据2、压测数据3、清理数据 五、结果解析六、最后 一、前言 做为一名性能工程师掌握对 MySQL 的性能测试是非常必要的,本文基于 Sysbench 对MySQL OLTP(联…

YOLOv8中的C2f模块

文章目录 一、结构概述二、模块功能 一、结构概述 C2f块:首先由一个卷积块(Conv)组成,该卷积块接收输入特征图并生成中间特征图特征图拆分:生成的中间特征图被拆分成两部分,一部分直接传递到最终的Concat块,另一部分传递到多个Botleneck块进…

uniapp(全端兼容) - 最新详细实现刻度尺组件效果,uni-app实现尺子打分及手指拖动刻度尺打分评分功能,可左右滑动刻度尺改变数值、带刻度尺滑块功能、

效果图 在uniapp微信小程序/手机h5网页网站/安卓app/苹果app/支付宝小程序/nvue等(全平台完美兼容)开发中,实现uniApp各端都兼容的 “刻度尺(横格尺 | 尺子)” 手势左右两侧拖动、手指滑动刻度尺功能,水平刻度尺,支持自定义尺子颜色、大小、刻度、滑动时的步进值、最大…

【Java】已解决java.net.ConnectException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.net.ConnectException异常 在Java的网络编程中,java.net.ConnectException是一个常见的异常,它通常表明在尝试建立网络连接时出现了问题。本文将…

“循环购“:快消品行业的创新商业模式引领者

大家好,我是吴军,来自一家在软件开发与商业模式创新领域享有盛誉的公司。我们专注于为企业提供全方位的商城系统搭建及商业模式定制服务。迄今为止,我们已经成功地为众多企业打造了超过200种独特的商业模式,助力他们实现了显著的商…

如何跳出认知偏差,个人认知能力升级

一、教程描述 什么是认知力?认知力(cognitive ability),实际上就是指一个人的认知能力,是指人的大脑加工、储存和提取信息的能力,或者主观对非主观的事物的反映能力,如果变成大白话&#xff0c…

Dev Eco Studio设置中文界面

Settings-Plugins-installed-搜索Chinese

WindTerm软件的本地模式和远程模式

WindTerm作为一个多功能的远程终端控制软件,支持本地模式和远程模式两种键盘输入处理方式,这两种模式的主要区别在于键盘输入的处理逻辑和目标: 本地模式(Local Mode) 在本地模式下,WindTerm不对键盘输入…

Android设计模式系列--模板方法模式

认识到模板方法的这种思想,父类可以让未知的子类去做它本身可能完成的不好或者根本完成不了的事情,对框架学习大有帮助。 本文以View中的draw方法为例,展开分析。 模板方法,TemplateMethod,光是学习这个模式&#xf…

Flutter第十二弹 Flutter多平台运行

目标: 1.在多平台调试启动Flutter程序运行 一、安卓模拟器 1.1 检查当前Flutter适配的版本 flutter doctor提供了Flutter诊断。 $ flutter doctor --verbose /Users/zhouronghua/IDES/flutter/bin/flutter doctor --verbose [✓] Flutter (Channel master, 2.1…

npm 安装踩坑

1 网络正常,但是以前的老项目安装依赖一直卡住无法安装?哪怕切换成淘宝镜像 解决办法:切换成yarn (1) npm i yarn -g(2) yarn init(3) yarn install在安装的过程中发现: [2/4] Fetching packages... error marked11.1.0:…

2025届阳光保险集团应届生校招社招入职测评真题题库北森自适应测评题库

第1题 人类使用塑料袋的历史很短,但对塑料袋的指责却不绝于耳。全世界每年要消耗5000亿到1万亿个塑料袋。废弃的塑料袋被掩埋会影响农作物吸收营养和水分,污染地下水;焚烧塑料袋则会产生有毒气体,影响人体健康。因此如何处理塑料袋十分重要。…

电路仿真实战设计教程--平均电流控制原理与仿真实战教程

1.平均电流控制原理: 平均电流控制的方块图如下,其由外电路电压误差放大器作电压调整器产生电感电流命令信号,再利用电感电流与电流信号的误差经过一个电流误差放大器产生PWM所需的控制电压,最后由控制电压与三角波比较生成开关管的驱动信号。 2.电流环设计: 根据状态平…

基于chatgpt-on-wechat搭建个人知识库微信群聊机器人

前言 啊,最近在别人微信群里看到一个聊天机器人,感觉挺好玩的。之前GPT刚出来的时候就知道有人把聊天机器人接入到微信或者QQ中来增加互动,但是当时没想那个想法。 很久没关注这块了,发现现在可以使用大模型知识库的方式来打造自…

微服务改造启动多个 SpringBoot 的陷阱与解决方案

在系统运行了一段时间后,业务量上升后,生产上发现java应用内存占用过高,服务器总共64G,发现每个SpringBoot占用近12G的内存,我们项目采用微服务架构,有多个springboot应用。 一下子内存就不够用了&#xf…

MYSQL 四、mysql进阶 2(mysql逻辑架构以及查询流程)

一、mysql的逻辑架构 1. 逻辑架构剖析 1.1 服务器处理客户端请求 mysql是典型的c/s架构,即 client/server 架构,不论是客户端进程和服务器进程是采用哪种方式进行通信,最后实现的效果都是:客户端进程向服务器进程发送一段文本&am…

【D3.js in Action 3 精译】关于本书

文章目录 本书读者本书结构与路线图本书代码liveBook 在线论坛 D3.js 项目的传统开发步骤 本书读者 这本书适用于所有渴望在数据可视化工作中获得完全创意自由的人,从定制化的经典图表到创建独特的数据可视化布局,涵盖内容广泛,应有尽有。您…

RabbitMQ —— 理解及应用场景

一、MQ相关的概念 RabbitMQ 是一种分布式消息中间件,消息中间件也称消息队列MQ,那么什么是MQ呢?请继续阅读下文。 1.1、MQ的基本概念 什么是MQ MQ(message queue),从字面意思上看就个 FIFO 先入先出的队列,只不过队列…