Netty 源码研读 Phase0:准备环境、拉取源码与跑通 echo
你下定决心读 Netty 源码,兴冲冲 clone 下来、打开 IDE——然后卡住三连:clone 哪个版本?为什么满屏模块不知道 import 哪个?官方那个 echo 例子到底怎么跑起来?本文把这套地基一次铺平,让你后面 30 篇能直接点进源码、能跑、能对照。
本文所有命令与现象均经过实跑验证:源码在沙箱(JDK 17)编译跑通 echo,并在读者本机(Windows + IntelliJ + Microsoft Build of OpenJDK 11.0.32)完整复现——包括下文列出的每一处编译/运行坑。
一、Phase0 是地基,不是源码分析
- 是什么:Phase0 不分析任何 Netty 内部机制,只解决一件事——环境就位:版本锁定、IDE 能跳转、最小例子能跑。
- 坑:很多人一上来就读
ChannelPipeline源码,结果连代码都没 clone 对版本,类名对不上、行号对不上,越读越像在背天书。 - 本质一句话:读源码的前提是"源码在你机器上能点得进去、能跑起来",否则全是脑补。
二、锁定版本:为什么是 netty-4.1.110.Final
- 是什么:Netty 有两条大线——
4.1(长期主线,生产首选)和5(官方已废弃,不跟)。本系列锁4.1的稳定 tagnetty-4.1.110.Final。 - 坑:Netty 的
main分支每天都在变,类名和方法行号会漂移;今天文章写"第 66 行b.bind()",过两个月可能对到别的实现上。 - 本质一句话:读源码必须锁死一个 tag,文章贴的行号才可复现、可对照。
- 怎么拉(浅克隆,省空间,读源码不需要完整提交历史):
1 | git clone --depth 1 --branch netty-4.1.110.Final https://github.com/netty/netty.git |
注:若本机 GitHub 直连受限(写作环境即如此),可用 Gitee 镜像
https://gitee.com/mirrors/netty.git的同名 tag 拉取,内容与官方一致。克隆完成后git describe --tags应显示netty-4.1.110.Final。
三、仓库结构:你要读的代码在哪
Netty 是典型的 Maven 多模块工程,根目录一个 pom.xml 统管 20+ 模块。看清模块分工,才知道后面每篇钻哪。
common:基础工具与抽象(io.netty.util、引用计数、InternalThreadLocal等)。注意它pom.xml里把maven.compiler.target写死为1.6(最大兼容老 JVM)——这是后面 IDEA 编译坑的元凶,见第五节。buffer:ByteBuf体系(Phase5 重点)。transport:核心传输层——Channel/EventLoop/Unsafe(Phase2–3 重点)。codec*:各类编解码器(http/http2/mqtt/redis…,Phase6 重点)。handler:通用ChannelHandler(SSL、空闲检测、日志等)。example:可运行例子集合,本文的 echo 就在这里。坑:新手只
Open某个子模块目录,结果依赖不全、符号全红。必须导入根 pom 整个工程。本质一句话:根 pom 一次导入,全部模块与依赖就位,点哪个类都能跳转。
四、IDE 导入:以 Maven 工程打开根 pom
- 是什么:用 IntelliJ IDEA(Community 版即可)把仓库当作 Maven 项目导入。
- 步骤:
File → Open→ 选中仓库根目录(里面有pom.xml)→ 选择"作为 Project 打开" → 等待 Maven 依赖解析完成(首次会下载一阵)。 - 关键预警:导入后 IDEA 会按各模块
pom.xml的maven.compiler来定语言级别,而netty-common是target=1.6,会把一部分模块压到 language level 6。所以导入后务必按第五节把 Project SDK 与所有模块的 Language level 都设成 8/11,否则后面一片红。 - 本质一句话:导入根 pom = 一次性拿到全部模块与依赖,源码随便跳。
五、本机环境约定(实跑验证,不是想当然)
翻根 pom.xml,主线 maven.compiler.source/target = 1.8;但 netty-common 模块为了最大兼容老 JVM,单独把 maven.compiler.target 设成了 1.6。别小看这一行——它是 IDEA 全模块导入时几乎所有编译报错的源头。
5.1 JDK 版本:必须 8 或 11,不能用 12+
java.nio.file 是 Java 7 的 API,而 -target 6 在 JDK 12 起被 javac 彻底移除支持(报"无法编译为 JVM 目标 6")。因此:
| 你选的 JDK | 结果 |
|---|---|
| JDK 8 / 9 / 10 / 11 | ✅ 支持 -target 1.6,可编译全部模块 |
| JDK 17 / 21 / 26 等 12+ | ❌ 报 无法编译为 JVM 目标 6(netty-common 模块) |
实测:读者本机最初用 Oracle OpenJDK 26,IDEA 直接报
java: 无法编译为 JVM 目标 6 配置的模块 'netty-common';换成 Microsoft Build of OpenJDK 11.0.32(IDEA 自带 Download JDK 即可获取)后编译通过。本系列推荐 JDK 11。
5.2 IDEA 导入后必做三件事(否则满屏红)
- Project SDK = JDK 11:
File → Project Structure → Project → SDK选 11(没有就Add SDK → Download JDK选 11)。 - 所有模块 Language level 同步到 8/11:
Project Structure → Modules左侧Ctrl+A全选 →Sources标签 → Language level 设 8 或 11。光换 SDK 不够——netty-common的target=1.6会把它的 language level 压到 6,于是transport等用到java.nio.file(Java 7 API)的模块就报程序包 java.nio.file 不存在;连netty-common自己若被压到 6,下游还会报io.netty.util.collection 不存在。这两个错都是 language level 没拉齐的连锁反应。 - 模块依赖要接上:
Maven工具窗右键根pom.xml→ Reload Project,让 IDEA 重建 module-to-module 依赖图;再 Build → Rebuild Project 按依赖顺序全量编译(common 先出classes,下游才找得到)。若仍报io.netty.*(netty 自身类)不存在,就是 common 没产出 classes 或依赖没接——Reload + Rebuild 基本能解。
| 项目 | 要求 | 说明 |
|---|---|---|
| JDK | 8 或 11(必须 ≤11) | 12+ 因 netty-common 的 target=1.6 直接报错 |
| Language level | 所有模块统一 8/11 | 不能留 6,否则 java.nio.file / io.netty.util.collection 报不存在 |
| 构建 | 仓库自带 mvnw / mvnw.cmd |
优先用包装器,免装 Maven;也可本机装 Maven 3.6+ |
| IDE | IntelliJ IDEA(Community 足够) | 本文以 IDEA 为例 |
| 源码版本 | netty-4.1.110.Final | 已浅克隆并锁定 |
六、跑通 echo:最小可运行标本
- 是什么:
echo是 Netty 官方最简例子——服务端收到什么字节,原样回显给客户端。它是理解Channel/Pipeline/EventLoop的最小标本,一条链路把后面要讲的核心组件全亮了相。 - 真实路径(已核对):
example/src/main/java/io/netty/example/echo/EchoServer.java/EchoServerHandler.javaEchoClient.java/EchoClientHandler.java
- 怎么启动(推荐方式 A;想用命令行见方式 B):
- 方式 A(IDEA Run,已在本机实测跑通):先按第五节设好 JDK 11 + Language level 8/11 并 Rebuild;然后右键
EchoServer的main()→Run,再右键EchoClient的main()→Run。服务端控制台会打印BIND: 0.0.0.0/0.0.0.0:8007/ACTIVE,即成功。 - 方式 B(命令行,需本机装 Maven):在仓库根目录的 Git Bash 里执行
./run-example.sh echo-server(脚本会cd example后调用mvn exec:exec,自动带上正确主类);客户端同理./run-example.sh echo-client。
- 方式 A(IDEA Run,已在本机实测跑通):先按第五节设好 JDK 11 + Language level 8/11 并 Rebuild;然后右键
⚠️ Windows 命令行踩坑实录(实跑验证,共 5 条)
.sh别在 PowerShell/双击跑:Windows 可能把.sh关联到 Node,会掉进 Node REPL(看到Welcome to Node.js);必须bash run-example.sh echo-server显式用 Git Bash 执行。mvnw.cmd会把-DexampleClass=全限定名的=拆掉:PowerShell/CMD 里手敲mvnw.cmd ... -DexampleClass=io.netty...时,Windows 把=当分隔符,Maven 收不到类名(报"找不到或无法加载主类"且类名为空)。命令行务必走 Git Bash + 已安装的mvn(即方式 B),别在 PowerShell 手敲mvnw.cmd带-D类名。- PowerShell 跑
mvnw.cmd须加.\前缀:PowerShell 默认不从当前目录加载命令,直接mvnw.cmd会报"无法将…识别为 cmdlet";要.\mvnw.cmd …。- WSL 里
./mvnw报^M: bad interpreter:克隆的mvnw是 CRLF,Linux 读 shebang 失败;sed -i 's/\r$//' mvnw去回车即可(且 WSL 需另配 JDK)。- 根目录直接
mvnw exec:exec会失败:exec:exec会套到所有模块,netty-dev-tools等模块缺executable配置而报错;必须先cd example(正是run-example.sh的做法)。结论:想省事用法 A(IDEA Run);想走命令行就 Git Bash + 装好的 Maven +
./run-example.sh,别在 PowerShell 里手敲mvnw.cmd带-D类名。
6.1 "看不到回显"是正常的——别误判没跑通
这是最容易让人以为"没跑通"的点,务必看清:
- 官方
EchoClientHandler.channelRead只有ctx.write(msg)(把收到的回显再写回服务器),没有任何打印语句;且EchoClient.java里客户端的LoggingHandler默认被注释掉。所以客户端控制台本来就是空的——"没回显"是 example 的正常设计,不代表链路断了。 - 服务端日志里
BIND / ACTIVE加上READ: ... R:/127.0.0.1:<客户端端口>表示客户端已连上,这就是跑通的证据。 - 想真正"看到"回显,给客户端加上日志即可:把
EchoClient.java第 60 行的注释//p.addLast(new LoggingHandler(LogLevel.INFO));去掉,并在文件头部补两行 import:重新 Run1
2import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.logging.LogLevel;EchoClient,窗口会刷出WRITE: 256B/READ/FLUSH事件(默认发的是 0x00–0xFF 共 256 字节二进制,不是明文字符串),证明数据在原样往返。 - 日志"不停"也是正常的:因为客户端
channelRead收到回显后又ctx.write写回服务器,服务器再回显,客户端再写回……形成无限 ping-pong。点 Run 窗口的红色方块(Stop)即可停止。
真实片段(EchoServer.java 第 35–66 行,已贴行号):
1 | public final class EchoServer { |
- 坑:① echo 默认监听
8007,别和本机其他服务抢端口(可用-Dport=xxxx改);②ServerUtil.buildSslContext()那段 SSL 是自签可选配置,不影响回显主逻辑,初读可忽略;③ 客户端不打印回显、且日志不停,是例子设计,不是 bug(见 6.1)。 - 本质一句话:
bind → accept → read → write一条链路,把ServerBootstrap/EventLoopGroup/Channel/Pipeline/Handler一次性摆在你眼前。
七、本系列读法约定
为避免"读完还是串不起来",全系列统一读法:
- 先骨架后血肉:Phase0/1 看全景(架构 + 启动流程),再下钻 Phase3–5(事件循环 / Pipeline / 内存),最后 Phase7 把读/写/连接主链路串一遍、Phase8 收口。
- 每篇固定结构:开场钩子 →「是什么 / 坑 / 本质一句话」→ 真实源码片段 + 行号 → 可运行 demo → 裸内联 SVG 图 → 对比小结。
- 两条铁律:图一律用裸内联 SVG(不写 ASCII 图);文章内不预测下一篇——每篇自成一体。
八、小结
Phase0 结束时,你应该已经:
- ✅ 源码锁在
netty-4.1.110.Final,git describe --tags可验证; - ✅ IDEA 以 Maven 工程导入根 pom,Project SDK = JDK 11、所有模块 Language level = 8/11、模块依赖已接(否则会卡在 target 1.6 / java.nio.file / io.netty.util.collection 三连报错);
- ✅
EchoServer跑起来打印BIND 8007/ACTIVE,EchoClient连上后数据在 ping-pong 往返(想看回显就给客户端加LoggingHandler+ 两行 import)。
地基就位,后面就能安心逐行走读了。

