【Netty篇】Handler Pipeline 详解

目录
- 一、 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(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("这是第 1 道入站工序");// 将消息传递给管道中的下一个入站处理器ctx.fireChannelRead(msg); // 1}});ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("这是第 2 道入站工序");// 将消息传递给管道中的下一个入站处理器ctx.fireChannelRead(msg); // 2}});ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("这是第 3 道入站工序");// 将消息写入管道,准备发送给客户端ctx.channel().write(msg); // 3}});// 添加出站处理器,处理待发送的消息ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg,ChannelPromise promise) {System.out.println("这是第 1 道出站工序");// 继续处理写操作,将消息写入管道ctx.write(msg, promise); // 4}});ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg,ChannelPromise promise) {System.out.println("这是第 2 道出站工序");// 继续处理写操作,将消息写入管道ctx.write(msg, promise); // 5}});ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){@Overridepublic 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>() {@Overrideprotected void initChannel(Channel ch) {// 添加字符串编码器到管道,以便发送字符串消息ch.pipeline().addLast(new StringEncoder());}})// 连接到指定的主机和端口.connect("127.0.0.1", 8080)// 添加监听器,在连接成功后执行操作.addListener((ChannelFutureListener) future -> {// 发送消息到服务器future.channel().writeAndFlush("hello,world");});}
}
分别运行服务端和客户端,查看运行结果

3、 执行顺序详解
-
入站处理器(Inbound)
数据刚刚从客户端传入时,会依次触发三个入站 Handler:- 第一个 Handler 打印
这是第 1 道入站工序,调用ctx.fireChannelRead(msg)传给下一个 Handler。 - 第二个 Handler 打印
这是第 2 道入站工序,同样调用ctx.fireChannelRead(msg)。 - 第三个 Handler打印
这是第 3 道入站工序,然后调用ctx.channel().write(msg)触发出站处理器。
- 第一个 Handler 打印
-
出站处理器(Outbound)
当第三个入站 Handler 调用ctx.channel().write(msg)时,它开始从 Pipeline 尾部查找出站处理器,并依次执行:- 最后一个出站 Handler(Handler6)打印
这是第 3 道出站工序,调用ctx.write(msg, promise)将消息交给前一个出站 Handler; - 然后 Handler5 打印
这是第 2 道出站工序; - 接着 Handler4 打印
这是第 1 道出站工序。
完成后消息会真正被写出到 Channel 中。这里的关键是:出站 Handler 的执行顺序与它们添加的顺序相反,也就是从尾部开始触发。
- 最后一个出站 Handler(Handler6)打印
-
输出流程图

这就完美地展现了入站与出站的“正序与逆序”运行机制。😎
补充说明: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 有全新的认识。😄
相关文章:
【Netty篇】Handler Pipeline 详解
目录 一、 Handler & Pipeline——流水线上的“特种部队”与“生产线”1、 ChannelHandler —— 流水线上的“特种兵”👮♂️2、 ChannelPipeline —— 生产线上的“接力赛跑”🏃♀️🏃♂️ 二、 代码实例1、 服务端代码示例2、 客…...
计算机网络 - UDP协议
通过一些问题来讨论 UDP 协议 什么是 UDP?举几个应用了 UDP 协议的例子UDP 与 TCP 有啥区别?(PS:介绍三四个就可以了,不用说太多)具体 UDP 是不可靠的,那你觉得如何实现一个可靠的 UDP &#x…...
16-算法打卡-哈希表-两个数组的交集-leetcode(349)-第十六天
1 题目地址 349. 两个数组的交集 - 力扣(LeetCode)349. 两个数组的交集 - 给定两个数组 nums1 和 nums2 ,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 示例 1:输入:nu…...
java + spring boot + mybatis 通过时间段进行查询
前端传来的只有日期内容,如:2025-04-17 需要在日期内容的基础上补充时间部分,代码示例: /*** 日志查询(分页查询)* param recordLogQueryDTO 查询参数对象* return 日志列表*/Overridepublic PageBean<…...
AI与物联网的深度融合:开启智能生活新时代
在当今数字化时代,人工智能(AI)和物联网(IoT)作为两大前沿技术,正在加速融合,为我们的生活和工作带来前所未有的变革。这种融合不仅提升了设备的智能化水平,还为各行各业带来了新的机…...
浔川AI翻译v7.0更新预告
亲爱的浔川AI翻译用户: 感谢您一直以来的支持!浔川AI翻译自推出以来,已迭代6个版本,其中**v2.0和v4.0因技术问题(翻译结果显示异常、注册失败、密码找回功能失效等)**被迫下架。我们深知这些问题影响了您…...
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 入门
大家好!我是心海 流处理技术在大数据时代正变得越来越重要,而 Apache Flink 作为领先的流处理引擎,凭借其高性能、低延迟和丰富的 API 受到了广泛关注。本文将以 Scala 语言为例,详细讲解 Flink DataStream API 的基本编程模型&am…...
实战|使用环信Flutter SDK构建鸿蒙HarmonyOS应用及推送配置
本文为大家介绍如何在 Flutter 环境创建 Harmony 项目并集成环信即时通讯IM以及环信 Flutter Harmony 推送配置。 已经基于环信的 Flutter 项目也可以参考本文适配鸿蒙端。 一、开发环境要求 前置条件 1.安装DevEco-Studio 2.安装模拟器 DevEco-Studio 下载与操作指导&…...
HTML5好看的水果蔬菜在线商城网站源码系列模板5
文章目录 1.设计来源1.1 主界面1.2 关于我们1.3 商品服务1.4 果蔬展示1.5 联系我们1.6 商品具体信息1.7 登录注册 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板,程序开发,在线开发,在线沟通 作者:xcLeigh 文章地址&#…...
宜搭与金蝶互通——连接器建立
一、 进入连接器工厂 图1 连接器入口 二、 新建连接器 图2 新建连接器第一步 1、 连接器显示名,如图2中①所示; 2、 图2中②域名,是金蝶系统API接口里面的“完整服务地址”com之前的信息,不含“https”,如图3中①所示; 3、 Base Url通常为“/”,如图2…...
SP7733:HPYNOS - Happy Numbers I(参考我之前的文章,哈希)
题目大意 我们定义“破坏”整数的过程是对其每一位上的数字的平方求和成为一个新数,如果一个数在经过若干次“破坏”以后变成了 1,那么这个数就是一个高兴的数字,输出变化次数,否则如果永远不会变成 1,输出 −1。 例如…...
《Java 泛型的作用与常见用法详解》
大家好呀!👋 今天我们要聊的是Java中一个超级重要但又让很多初学者头疼的概念——泛型(Generics)。带你彻底搞懂它!💪 准备好你的小本本,我们开始啦~📝 一、为什么需要泛型?&#x…...
【JavaWeb】详细讲解 HTTP 协议
文章目录 一、HTTP简介1.1 概念1.2 特点 二、协议2.1 HTTP-请求协议(1)GET方式(2)POST方式(3)GET和POST的区别: 2.2 HTTP-响应协议(1)格式(2)响应…...
【android bluetooth 框架分析 02】【Module详解 4】【Btaa 模块介绍】
1. 背景 我们在上一篇文章中介绍 HciHal 模块时,有如下代码 // system/gd/hal/hci_hal_android_hidl.ccvoid ListDependencies(ModuleList* list) const {list->add<SnoopLogger>();if (common::init_flags::btaa_hci_is_enabled()) {list->add<ac…...
“星睿O6” AI PC开发套件评测 - Windows on Arm 安装指南和性能测评
引言 Radxa联合此芯科技和安谋科技推出全新的"星睿O6"迷你 ITX 主板。该系统搭载了 CIX P1(CD8180)12 核 Armv9 处理器,拥有高达30T算力的NPU和高性能的GPU,最高配备64GB LPDDR内存,并提供了如 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)# 推理(可以传文件路径…...
数据通信学习笔记之OSPF路由汇总
区域间路由汇总 路由汇总又被称为路由聚合,即是将一组前缀相同的路由汇聚成一条路由,从而达到减小路由表规模以及优化设备资源利用率的目的,我们把汇聚之前的这组路由称为精细路由或明细路由,把汇聚之后的这条路由称为汇总路由或…...
ASP.NET Core Web API 配置系统集成
文章目录 前言一、配置源与默认设置二、使用步骤1)创建项目并添加配置2)配置文件3)强类型配置类4)配置Program.cs5)控制器中使用配置6)配置优先级测试7)动态重载配置测试8)运行结果示…...
如何判断单片机性能极限?
目录 1、CPU 负载 2、内存使用情况 3、实时性能 4、外设带宽 5、功耗与温度 在嵌入式系统设计中,当系统变得复杂、功能增加时,单片机可能会逐渐逼近其性能极限。及时识别这些极限点对于保证产品质量、稳定性和用户体验至关重要。 当你的嵌入式系统…...
AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析
以下是 AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析: 1. 多Agent协同的定义与核心目标 多Agent系统(MAS, Multi-Agent System): 由多个独立或协作的智能体(Agent)组成ÿ…...
1.凸包、极点、极边基础概念
目录 1.凸包 2.调色问题 3.极性(Extrem) 4.凸组合(Convex Combination) 5.问题转化(Strategy)编辑 6.In-Triangle test 7.To-Left-test 8.极边(Extream Edges) 1.凸包 凸包就是上面蓝色皮筋围出来的范围 这些钉子可以转换到坐标轴中࿰…...
OSCP - Proving Grounds - DriftingBlues6
主要知识点 路径爆破dirtycow内核漏洞提权 具体步骤 总体来讲,这台靶机还是比较直接的,没有那么多的陷阱,非常适合用来学习 依旧是nmap开始,只开放了80端口 Nmap scan report for 192.168.192.219 Host is up (0.42s latency). Not shown: 65534 cl…...
深度理解指针之例题
文章目录 前言题目分析与讲解涉及知识点 前言 对指针有一定了解后,讲一下一道初学者的易错题 题目分析与讲解 先定义一个数组跟一个指针变量 然后把数组名赋值给指针变量————也就是把首地址传到pulPtr中 重点是分析这一句: *(pulPtr…...
java 设计模式之策略模式
简介 策略模式:策略模式可以定制目标对象的行为,它尅通过传入不同的策略实现,来配置目标对象的行为。使用策略模式,就是为了定制目标对象在某个关键点的行为。 策略模式中的角色: 上下文类:持有一个策略…...
LeetCode算法题(Go语言实现)_51
题目 给你两个下标从 0 开始的整数数组 nums1 和 nums2 ,两者长度都是 n ,再给你一个正整数 k 。你必须从 nums1 中选一个长度为 k 的 子序列 对应的下标。 对于选择的下标 i0 ,i1 ,…, ik - 1 ,你的 分数 …...
条件变量condition_variable
1.条件变量用来控制线程同步和协调。 2.调用wait方法,如果条件满足就不挂起,如果条件不满足就挂起线程并释放锁。 3.等到通知后,挂起线程先 竞争锁,然后 判断条件,如果满足条件就往下走,如果不满足就再次挂起并释放锁…...
Solon AI MCP Server 入门:Helloworld (支持 java8 到 java24。国产解决方案)
目前网上能看到的 MCP Server 基本上都是基于 Python 或者 nodejs ,虽然也有 Java 版本的 MCP SDK,但是鲜有基于 Java 开发的。 作为Java 开发中的国产顶级框架 Solon 已经基于 MCP SDK 在进行 Solon AI MCP 框架开发了,本文将使用 Solon AI …...
Maven工具学习使用(十一)——部署项目到仓库
1、使用Maven默认方式 Maven 部署项目时默认使用的上传文件方式是通过 HTTP/HTTPS 协议。要在 Maven 项目中配置部署,您需要在项目的 pom.xml 文件中添加 部分。这个部分定义了如何部署项目的构件(如 JAR 文件)到仓库。。这个部分定义了如何…...
公司内部自建知识共享的方式分类、详细步骤及表格总结,分为开源(对外公开)和闭源(仅限内部),以及公共(全员可访问)和内部(特定团队/项目组)四个维度
以下是公司内部自建知识共享的方式分类、详细步骤及表格总结,分为开源(对外公开)和闭源(仅限内部),以及公共(全员可访问)和内部(特定团队/项目组)四个维度&am…...

