【Netty篇】Handler Pipeline 详解

news2025/7/7 9:22:57

在这里插入图片描述

目录

    • 一、 Handler & Pipeline——流水线上的“特种部队”与“生产线”
      • 1、 ChannelHandler —— 流水线上的“特种兵”👮‍♂️
      • 2、 ChannelPipeline —— 生产线上的“接力赛跑”🏃‍♀️🏃‍♂️
    • 二、 代码实例
      • 1、 服务端代码示例
      • 2、 客户端代码示例
      • 3、 执行顺序详解
      • 补充说明:ctx.channel().write(msg) vs ctx.write(msg)
      • 顺序模拟
    • 三、 总结:特种兵与流水线的完美协作🎉

🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!

🌟了解 Netty 请看 : 【Netty篇】幽默的讲解带你入门 Netty !建议收藏

其他优质专栏: 【🎇SpringBoot】【🎉多线程】【🎨Redis】【✨设计模式专栏(已完结)】…等

如果喜欢作者的讲解方式,可以点赞收藏加关注,你的支持就是我的动力
✨更多文章请看个人主页: 码熔burning

准备好迎接一场别开生面的流水线派对了吗?😄 今天我们要用幽默风趣的方式来聊聊 Netty 中的 Handler 和 Pipeline,它们在 Netty 中究竟扮演什么角色,又如何协同配合,把数据“打造成产品”。


了解 Netty 的 Channel 请看:【Netty篇】Channel 详解
了解 Netty 的 Future & Promise 请看:【Netty篇】Future & Promise 详解


一、 Handler & Pipeline——流水线上的“特种部队”与“生产线”

1、 ChannelHandler —— 流水线上的“特种兵”👮‍♂️

在 Netty 中,每个 ChannelHandler 就像一名训练有素的特种兵,负责处理 Channel 上的各种事件。它们可以分为两大阵营:

  • 入站处理器(Inbound Handler)
    通常继承自 ChannelInboundHandlerAdapter,专门负责处理从网络上“进厂”的原材料——比如读取客户端发送的数据、进行解码、执行业务逻辑,并在处理完毕后调用 ctx.fireChannelRead(msg) 把数据往后传递。

  • 出站处理器(Outbound Handler)
    通常继承自 ChannelOutboundHandlerAdapter,主要负责把数据“打包出厂”,对写回网络的数据进行编码、记录日志等处理。它们的调用顺序与入站处理器正好相反(逆序执行)。

简单来说,当数据在流水线上运动时,入站 Handler 就像一道道工序,在数据“进厂”时依次对它进行处理;而当数据需要“出厂”发往客户端时,则经过一系列出站 Handler 的“最后打扮”,保证成品美美哒地呈现!🍔

2、 ChannelPipeline —— 生产线上的“接力赛跑”🏃‍♀️🏃‍♂️

ChannelPipeline 则是把所有这些 Handler 组织在一起的数据处理链,它本质上就是一个双向链表,每个节点是一个包装了 ChannelHandler 的 ChannelHandlerContext。在这条流水线上:

  • 入站数据 按添加顺序(从头到尾)依次经过每个 Handler,就像在接力赛中顺次把接力棒传给下一个队友;
  • 出站数据 则会以相反的顺序(从尾到头)经过所有出站 Handler,就像在倒着跑的接力赛中,把数据从尾端“传回”前端进行最后处理。

光看的话比较单调,直接上代码,我们来详细解读代码和顺序吧!


二、 代码实例

1、 服务端代码示例

下面这段代码在服务端注册了 3 个入站 Handler 和 3 个出站 Handler。我们来看一下每个 Handler 的作用和执行流程。

public class NetServer {
    public static void main(String[] args) {
        // 配置服务器引导类,用于启动和绑定网络服务器
        new ServerBootstrap()
                // 设置事件循环组,处理I/O操作
                .group(new NioEventLoopGroup())
                // 指定通道类型为NIO服务器Socket通道
                .channel(NioServerSocketChannel.class)
                // 设置通道初始化器,用于配置管道处理逻辑
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    /**
                     * 初始化通道,设置通道的管道处理器
                     * @param ch 要初始化的通道
                     */
                    protected void initChannel(NioSocketChannel ch) {
                        // 添加入站处理器,处理接收到的消息
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                System.out.println("这是第 1 道入站工序");
                                // 将消息传递给管道中的下一个入站处理器
                                ctx.fireChannelRead(msg);     // 1
                            }
                        });
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                System.out.println("这是第 2 道入站工序");
                                // 将消息传递给管道中的下一个入站处理器
                                ctx.fireChannelRead(msg);     // 2
                            }
                        });
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                System.out.println("这是第 3 道入站工序");
                                // 将消息写入管道,准备发送给客户端
                                ctx.channel().write(msg);     // 3
                            }
                        });
                        // 添加出站处理器,处理待发送的消息
                        ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg,
                                              ChannelPromise promise) {
                                System.out.println("这是第 1 道出站工序");
                                // 继续处理写操作,将消息写入管道
                                ctx.write(msg, promise);      // 4
                            }
                        });
                        ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg,
                                              ChannelPromise promise) {
                                System.out.println("这是第 2 道出站工序");
                                // 继续处理写操作,将消息写入管道
                                ctx.write(msg, promise);     // 5
                            }
                        });
                        ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg,
                                              ChannelPromise promise) {
                                System.out.println("这是第 3 道出站工序");
                                // 继续处理写操作,将消息写入管道
                                ctx.write(msg, promise);       // 6
                                
                            }
                        });
                    }
                })
                // 绑定端口8080,启动服务器
                .bind(8080);
    }
}

2、 客户端代码示例

public class NetClient {
    public static void main(String[] args) {
        // 配置客户端
        new Bootstrap()
                // 指定事件循环组,处理I/O操作
                .group(new NioEventLoopGroup())
                // 指定通道类型
                .channel(NioSocketChannel.class)
                // 设置通道初始化器,配置管道处理器
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) {
                        // 添加字符串编码器到管道,以便发送字符串消息
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                // 连接到指定的主机和端口
                .connect("127.0.0.1", 8080)
                // 添加监听器,在连接成功后执行操作
                .addListener((ChannelFutureListener) future -> {
                    // 发送消息到服务器
                    future.channel().writeAndFlush("hello,world");
                });
    }
}

分别运行服务端和客户端,查看运行结果

在这里插入图片描述

3、 执行顺序详解

  1. 入站处理器(Inbound)
    数据刚刚从客户端传入时,会依次触发三个入站 Handler:

    • 第一个 Handler 打印 这是第 1 道入站工序,调用 ctx.fireChannelRead(msg) 传给下一个 Handler。
    • 第二个 Handler 打印 这是第 2 道入站工序,同样调用 ctx.fireChannelRead(msg)
    • 第三个 Handler打印 这是第 3 道入站工序,然后调用 ctx.channel().write(msg) 触发出站处理器。
  2. 出站处理器(Outbound)
    当第三个入站 Handler 调用 ctx.channel().write(msg) 时,它开始从 Pipeline 尾部查找出站处理器,并依次执行:

    • 最后一个出站 Handler(Handler6)打印 这是第 3 道出站工序,调用 ctx.write(msg, promise) 将消息交给前一个出站 Handler;
    • 然后 Handler5 打印 这是第 2 道出站工序
    • 接着 Handler4 打印 这是第 1 道出站工序

    完成后消息会真正被写出到 Channel 中。这里的关键是:出站 Handler 的执行顺序与它们添加的顺序相反,也就是从尾部开始触发。

  3. 输出流程图

在这里插入图片描述

这就完美地展现了入站与出站的“正序与逆序”运行机制。😎

补充说明:ctx.channel().write(msg) vs ctx.write(msg)

  • ctx.channel().write(msg)
    从整个 Pipeline 的尾部开始查找出站处理器,这意味着总是会从最新添加的出站 Handler 开始执行。

  • ctx.write(msg)
    则从当前 Handler 的上一个出站 Handler 开始触发,如果在当前节点之前没有出站 Handler,则不会触发出站逻辑。

例如,在第三个入站 Handler中使用 ctx.channel().write(msg) 能确保出站 Handler 全部触发;如果改成 ctx.write(msg),而节点3自身后没有出站处理器,则可能只触发部分或不触发任何出站 Handler。

顺序模拟

上面的大致执行顺序你应该了解了,接下来实战一下看看具体效果是否真的是符合我们的想法。我已经在上面服务端代码标记好了需要操作的代码行,标记了 1、2、3、4、5、6,你需要先找到:

在这里插入图片描述

首先我将 1 处的代码注释掉,查看运行结果:

在这里插入图片描述

可以看到只打印了第一道工序,后面的都没有打印,接下来我把 1 处的代码注释回来,然后注释 2 处的代码,查看运行结果:

在这里插入图片描述

由输出结果咱可以得出结论:如果要调用下一个入站处理器,就必须使用 ctx.fireChannelRead(msg) 向下传递,出站也是同样的,你们可以自行的去试一下,比如注释掉 5 或者 6 来看一下输出结果

注意 : 你如果将 3 处的 ctx.channel().write(msg) 误写成了 ctx.write(msg),仅会打印 如下结果,因为节点 3 之前没有其它出站处理器了
在这里插入图片描述
6 处的 ctx.write(msg, promise) 误写成了 ctx.channel().write(msg) 会打印如下结果, 因为 ctx.channel().write() 是从尾部开始查找,结果又是节点 6 自己
在这里插入图片描述


三、 总结:特种兵与流水线的完美协作🎉

  • ChannelHandler 就像车间里的各路特种兵,不同的 Handler 负责不同的加工任务——有的负责“进料”(入站),有的负责“出货”(出站)。
  • ChannelPipeline 则是连接这些特种兵的生产线,就像流水线一样确保数据能够逐个被加工、转换后顺利送出。
  • 执行顺序
    • 入站:按照 Handler 添加的先后顺序依次处理(正序)。
    • 出站:按照 Handler 添加的逆序处理(倒序)。

这一切就好像是一场精心设计的接力赛,数据作为接力棒在各个 Handler 之间传递、加工,最终将高质量的产品“送达”客户端!🍔🚀

希望这份幽默而详细的讲解能让你对 Netty 的 Handler 和 Pipeline 有全新的认识。😄

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

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

相关文章

16-算法打卡-哈希表-两个数组的交集-leetcode(349)-第十六天

1 题目地址 349. 两个数组的交集 - 力扣&#xff08;LeetCode&#xff09;349. 两个数组的交集 - 给定两个数组 nums1 和 nums2 &#xff0c;返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 示例 1&#xff1a;输入&#xff1a;nu…

java + spring boot + mybatis 通过时间段进行查询

前端传来的只有日期内容&#xff0c;如&#xff1a;2025-04-17 需要在日期内容的基础上补充时间部分&#xff0c;代码示例&#xff1a; /*** 日志查询&#xff08;分页查询&#xff09;* param recordLogQueryDTO 查询参数对象* return 日志列表*/Overridepublic PageBean<…

helm账号密码加密

1、安装工具 sudo apt update sudo apt install gnupg -y wget https://github.com/getsops/sops/releases/download/v3.10.2/sops-v3.10.2.linux.amd64 mv sops-v3.10.2.linux.amd64 /usr/local/bin/sops chmod x /usr/local/bin/sops2、生成加密文件 gpg --full-generate-…

Flink 编程基础:Scala 版 DataStream API 入门

大家好&#xff01;我是心海 流处理技术在大数据时代正变得越来越重要&#xff0c;而 Apache Flink 作为领先的流处理引擎&#xff0c;凭借其高性能、低延迟和丰富的 API 受到了广泛关注。本文将以 Scala 语言为例&#xff0c;详细讲解 Flink DataStream API 的基本编程模型&am…

HTML5好看的水果蔬菜在线商城网站源码系列模板5

文章目录 1.设计来源1.1 主界面1.2 关于我们1.3 商品服务1.4 果蔬展示1.5 联系我们1.6 商品具体信息1.7 登录注册 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&#xff1a;xcLeigh 文章地址&#…

宜搭与金蝶互通——连接器建立

一、 进入连接器工厂 图1 连接器入口 二、 新建连接器 图2 新建连接器第一步 1、 连接器显示名,如图2中①所示; 2、 图2中②域名,是金蝶系统API接口里面的“完整服务地址”com之前的信息,不含“https”,如图3中①所示; 3、 Base Url通常为“/”,如图2…

SP7733:HPYNOS - Happy Numbers I(参考我之前的文章,哈希)

题目大意 我们定义“破坏”整数的过程是对其每一位上的数字的平方求和成为一个新数&#xff0c;如果一个数在经过若干次“破坏”以后变成了 1&#xff0c;那么这个数就是一个高兴的数字&#xff0c;输出变化次数&#xff0c;否则如果永远不会变成 1&#xff0c;输出 −1。 例如…

【JavaWeb】详细讲解 HTTP 协议

文章目录 一、HTTP简介1.1 概念1.2 特点 二、协议2.1 HTTP-请求协议&#xff08;1&#xff09;GET方式&#xff08;2&#xff09;POST方式&#xff08;3&#xff09;GET和POST的区别&#xff1a; 2.2 HTTP-响应协议&#xff08;1&#xff09;格式&#xff08;2&#xff09;响应…

“星睿O6” AI PC开发套件评测 - Windows on Arm 安装指南和性能测评

引言 Radxa联合此芯科技和安谋科技推出全新的"星睿O6"迷你 ITX 主板。该系统搭载了 CIX P1&#xff08;CD8180&#xff09;12 核 Armv9 处理器&#xff0c;拥有高达30T算力的NPU和高性能的GPU&#xff0c;最高配备64GB LPDDR内存&#xff0c;并提供了如 5GbE、HDMI …

Python 调用 YOLOv11 ONNX

Python 调用 YOLO ONNX 1 下载ONNX文件2 Python代码 1 下载ONNX文件 ONNX下载地址 2 Python代码 import cv2 from ultralytics import YOLOdef check(yolo:str, path:str):# 加载 YOLOv11model YOLO(yolo)# 读取图片img cv2.imread(path)# 推理&#xff08;可以传文件路径…

数据通信学习笔记之OSPF路由汇总

区域间路由汇总 路由汇总又被称为路由聚合&#xff0c;即是将一组前缀相同的路由汇聚成一条路由&#xff0c;从而达到减小路由表规模以及优化设备资源利用率的目的&#xff0c;我们把汇聚之前的这组路由称为精细路由或明细路由&#xff0c;把汇聚之后的这条路由称为汇总路由或…

ASP.NET Core Web API 配置系统集成

文章目录 前言一、配置源与默认设置二、使用步骤1&#xff09;创建项目并添加配置2&#xff09;配置文件3&#xff09;强类型配置类4&#xff09;配置Program.cs5&#xff09;控制器中使用配置6&#xff09;配置优先级测试7&#xff09;动态重载配置测试8&#xff09;运行结果示…

如何判断单片机性能极限?

目录 1、CPU 负载 2、内存使用情况 3、实时性能 4、外设带宽 5、功耗与温度 在嵌入式系统设计中&#xff0c;当系统变得复杂、功能增加时&#xff0c;单片机可能会逐渐逼近其性能极限。及时识别这些极限点对于保证产品质量、稳定性和用户体验至关重要。 当你的嵌入式系统…

AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析

以下是 AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析&#xff1a; 1. 多Agent协同的定义与核心目标 多Agent系统&#xff08;MAS, Multi-Agent System&#xff09;&#xff1a; 由多个独立或协作的智能体&#xff08;Agent&#xff09;组成&#xff…

1.凸包、极点、极边基础概念

目录 1.凸包 2.调色问题 3.极性(Extrem) 4.凸组合(Convex Combination) 5.问题转化(Strategy)​编辑 6.In-Triangle test 7.To-Left-test 8.极边&#xff08;Extream Edges&#xff09; 1.凸包 凸包就是上面蓝色皮筋围出来的范围 这些钉子可以转换到坐标轴中&#xff0…

OSCP - Proving Grounds - DriftingBlues6

主要知识点 路径爆破dirtycow内核漏洞提权 具体步骤 总体来讲&#xff0c;这台靶机还是比较直接的&#xff0c;没有那么多的陷阱,非常适合用来学习 依旧是nmap开始,只开放了80端口 Nmap scan report for 192.168.192.219 Host is up (0.42s latency). Not shown: 65534 cl…

深度理解指针之例题

文章目录 前言题目分析与讲解涉及知识点 前言 对指针有一定了解后&#xff0c;讲一下一道初学者的易错题 题目分析与讲解 先定义一个数组跟一个指针变量 然后把数组名赋值给指针变量————也就是把首地址传到pulPtr中 重点是分析这一句&#xff1a; *&#xff08;pulPtr…

LeetCode算法题(Go语言实现)_51

题目 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;两者长度都是 n &#xff0c;再给你一个正整数 k 。你必须从 nums1 中选一个长度为 k 的 子序列 对应的下标。 对于选择的下标 i0 &#xff0c;i1 &#xff0c;…&#xff0c; ik - 1 &#xff0c;你的 分数 …

Solon AI MCP Server 入门:Helloworld (支持 java8 到 java24。国产解决方案)

目前网上能看到的 MCP Server 基本上都是基于 Python 或者 nodejs &#xff0c;虽然也有 Java 版本的 MCP SDK&#xff0c;但是鲜有基于 Java 开发的。 作为Java 开发中的国产顶级框架 Solon 已经基于 MCP SDK 在进行 Solon AI MCP 框架开发了&#xff0c;本文将使用 Solon AI …

公司内部自建知识共享的方式分类、详细步骤及表格总结,分为开源(对外公开)和闭源(仅限内部),以及公共(全员可访问)和内部(特定团队/项目组)四个维度

以下是公司内部自建知识共享的方式分类、详细步骤及表格总结&#xff0c;分为开源&#xff08;对外公开&#xff09;和闭源&#xff08;仅限内部&#xff09;&#xff0c;以及公共&#xff08;全员可访问&#xff09;和内部&#xff08;特定团队/项目组&#xff09;四个维度&am…