孤舟笔记 互联网常用框架篇七 Netty中Pipeline工作原理是怎样的?Handler链是怎么串起来的
文章目录
个人网站
Netty 的 Pipeline 是处理网络数据的 流水线——请求进来,经过一个个 Handler 处理,最终响应回去。面试官问这题,他想听的是:Pipeline 的数据结构是什么?Inbound 和 Outbound 怎么区分?Handler 的执行顺序是怎样的?
先说结论
| 维度 | 说明 |
|---|---|
| 本质 | 双向链表,每个节点是一个 Handler |
| 方向 | Inbound(入站,读数据)和 Outbound(出站,写数据) |
| 核心接口 | ChannelHandler + ChannelHandlerContext |
| 设计模式 | 责任链模式 |
| 执行顺序 | Inbound 从头到尾,Outbound 从尾到头 |
一句话记住:Pipeline 是一条流水线,Inbound 是进来安检(从头到尾),Outbound 是出门打包(从尾到头)
Pipeline的数据结构
每个 Channel 有一个 Pipeline,Pipeline 内部是 双向链表:
HeadContext ←→ Handler1 ←→ Handler2 ←→ Handler3 ←→ TailContext
↑ ↑
Inbound开始 Outbound开始
Outbound结束 Inbound结束
- HeadContext:链表头部,Inbound 处理的起点,Outbound 处理的终点(最终执行 I/O 写操作)
- TailContext:链表尾部,Inbound 处理的终点(丢弃未消费的消息),Outbound 处理的起点
// Pipeline 的典型配置
ch.pipeline()
.addLast("decoder", new StringDecoder()) // Inbound
.addLast("encoder", new StringEncoder()) // Outbound
.addLast("handler", new MyBusinessHandler()); // Inbound
Inbound和Outbound的区别
| 方向 | 触发时机 | 执行顺序 | 典型 Handler |
|---|---|---|---|
| Inbound | 读数据、连接建立 | Head → Tail | Decoder、业务处理 |
| Outbound | 写数据、关闭连接 | Tail → Head | Encoder、日志 |
// Inbound Handler:处理入站数据
public class MyDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
out.add(in.readBytes(4)); // 👈 解码,传递给下一个 Inbound Handler
}
}
// Outbound Handler:处理出站数据
public class MyEncoder extends MessageToByteEncoder<String> {
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) {
out.writeBytes(msg.getBytes()); // 👈 编码,传递给下一个 Outbound Handler
}
}
就像工厂流水线:原材料进来(Inbound)从第一道工序到最后一道,成品出去(Outbound)从最后一道工序到第一道(打包、质检、出厂)。
Handler的执行流程
入站流程(读请求)
网卡数据到达
↓
HeadContext.channelRead() ← 读取原始 ByteBuf
↓
Decoder.channelRead() ← 解码成 String
↓
BusinessHandler.channelRead() ← 处理业务逻辑
↓
TailContext.channelRead() ← 兜底,丢弃未消费的消息
出站流程(写响应)
ctx.writeAndFlush(response)
↓
TailContext.write() ← Outbound 起点
↓
Encoder.write() ← 编码成 ByteBuf
↓
HeadContext.write() ← 最终执行 I/O 写操作
↓
数据写入网卡
关键:Inbound 从 Head 到 Tail,Outbound 从 Tail 到 Head。它们共享同一个链表,只是方向不同。
ChannelHandlerContext的作用
每个 Handler 被包装成 ChannelHandlerContext,它有两个关键能力:
1. 传递事件
public class HandlerA extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 处理逻辑...
ctx.fireChannelRead(msg); // 👈 传递给下一个 Inbound Handler
}
}
2. 触发 Outbound 操作
public class HandlerB extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String response = process(msg);
ctx.writeAndFlush(response); // 👈 从当前位置往 Head 方向找 Outbound Handler
}
}
注意 ctx.write() 和 channel.write() 的区别:
| 方法 | 触发点 | 效率 |
|---|---|---|
| ctx.write() | 从当前 Handler 往 Head 找 | 高,跳过前面的 Outbound |
| channel.write() | 从 Tail 开始往 Head 找 | 低,经过所有 Outbound |
Handler的顺序很重要
// ❌ 错误顺序:Encoder 在 Decoder 前面
pipeline.addLast("encoder", new StringEncoder()); // Outbound
pipeline.addLast("decoder", new StringDecoder()); // Inbound
pipeline.addLast("handler", new MyHandler()); // Inbound
// Inbound 执行:Head → Encoder(跳过) → Decoder → Handler → Tail ✅
// Outbound 执行:Tail → Handler(跳过) → Decoder(跳过) → Encoder → Head ✅
// 虽然结果相同,但语义上不直观
// ✅ 推荐顺序:先 Inbound 后 Outbound(或者 Decoder/Encoder 成对放)
pipeline.addLast("decoder", new StringDecoder()); // Inbound
pipeline.addLast("encoder", new StringEncoder()); // Outbound
pipeline.addLast("handler", new MyHandler()); // Inbound
最佳实践:解码器放前面,编码器放中间,业务处理放后面。
Pipeline 工作原理全景
数据结构
├── 双向链表(Head ←→ Handler ←→ Tail)
├── HeadContext —— Inbound 起点或 Outbound 终点
└── TailContext —— Inbound 终点或 Outbound 起点
执行方向
├── Inbound(入站)—— Head → Tail,处理读数据
└── Outbound(出站)—— Tail → Head,处理写数据
核心组件
├── ChannelHandler —— 业务逻辑接口
├── ChannelHandlerContext —— 上下文包装,事件传递
└── ChannelPipeline —— Handler 链容器
设计模式
└── 责任链模式 —— 每个 Handler 只处理自己关心的
口诀:Pipeline双向链,Head开头Tail结尾;
Inbound从头走到尾,Outbound从尾走到头;
Context负责传递事件,write从当前开始找;
解码器放前面先处理,编码器中间业务后
回答技巧与点评
标准回答:Netty Pipeline 是基于责任链模式的双向链表,每个节点是一个 ChannelHandlerContext(包装 ChannelHandler)。Inbound 事件(读数据)从 Head 到 Tail 顺序执行,Outbound 事件(写数据)从 Tail 到 Head 逆序执行。ctx.write() 从当前 Handler 开始往前找 Outbound Handler,channel.write() 从 Tail 开始找。通常解码器放前面、编码器放中间、业务 Handler 放后面。
加分回答
- ChannelInboundHandler vs ChannelOutboundHandler:两者是独立接口,一个 Handler 可以同时实现两个接口。比如一个日志 Handler 既记录入站请求(Inbound)又记录出站响应(Outbound)
- SimpleChannelInboundHandler:Netty 提供的便捷基类,自动释放消息引用(ReferenceCountUtil.release(msg)),避免内存泄漏。普通的 ChannelInboundHandlerAdapter 需要手动释放
- 异步执行:如果 Handler 中有耗时操作,可以通过 addLast(EventExecutorGroup, Handler) 将该 Handler 绑定到单独的线程池,不阻塞 Worker 的 I/O 线程
面试官点评
这道题考的是你对 Netty 数据处理流程 的理解。能说出双向链表和 Inbound/Outbound 方向算及格,高分的关键在于:讲清楚 ctx.write() 和 channel.write() 的区别,以及 Handler 顺序的影响。如果你能提到 SimpleChannelInboundHandler 的自动释放和异步线程池,说明你有 Netty 实战经验。
内容有帮助?点赞、收藏、关注三连!评论区等你 💪
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)