From 7c523880c4c007d36b92bb0d3ee0f2ead4c99022 Mon Sep 17 00:00:00 2001 From: IE619 <984680432@qq.com> Date: Mon, 25 May 2026 00:59:03 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java_base_test/io/nio/show_multi_agent/NIODemoMain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/NIODemoMain.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/NIODemoMain.java index 3977783..15f6d41 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/NIODemoMain.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/NIODemoMain.java @@ -219,7 +219,7 @@ * ├── Part22_NIO2Advanced.java * └── Part23_ModernJdkIOFeatures.java * - * ============================================================ + * ===xlb========================================================= */ public class NIODemoMain { From 3ec0186cb64eb3d1e2ce120624f95da14ff9e4b4 Mon Sep 17 00:00:00 2001 From: IE619 <984680432@qq.com> Date: Tue, 26 May 2026 00:26:07 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E5=AD=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java_base_test/io/nio/show_multi_agent/NIODemoMain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/NIODemoMain.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/NIODemoMain.java index 15f6d41..3977783 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/NIODemoMain.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/NIODemoMain.java @@ -219,7 +219,7 @@ * ├── Part22_NIO2Advanced.java * └── Part23_ModernJdkIOFeatures.java * - * ===xlb========================================================= + * ============================================================ */ public class NIODemoMain { From b891c7ed8f74cd69190ff3a1464f8381b31110e0 Mon Sep 17 00:00:00 2001 From: IE619 <984680432@qq.com> Date: Tue, 26 May 2026 18:59:05 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=E4=BA=94=E7=A7=8D=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=9A=84=E7=9F=A5=E8=AF=86=E7=82=B9=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../show_multi_agent/Part1_FiveIOModels.java | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part1_FiveIOModels.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part1_FiveIOModels.java index 18b7815..55033a1 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part1_FiveIOModels.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part1_FiveIOModels.java @@ -17,21 +17,33 @@ static void explain() { System.out.println(); System.out.println(" 模型① BIO → 你打电话给前台:「我要一份外卖,好了叫我」"); System.out.println(" 然后你就站在前台等,什么都不干,直到外卖到了才走"); - System.out.println(" (线程全程阻塞挂起,无法服务其他连接,线程资源白白占用)"); + System.out.println(" (线程全程阻塞挂起,无法服务其他连接,线程资源白白占用)");//此时CPU转向执行其他线程,当前线程被阻塞。 + System.out.println(" 【关系】1:1(一对一绑定),一个线程独占一个连接"); + System.out.println(" 【表现】连接数增加→线程数必须线性增加,阻塞期间不占CPU但占内存"); System.out.println(); System.out.println(" 模型② NIO轮询 → 你回工位,每隔30秒起来跑一趟前台问:「外卖到了吗?」"); System.out.println(" 没到就回去,30秒后再跑一趟。如此反复。"); - System.out.println(" (CPU 一直忙着轮询,浪费在大量无效的「没到」)"); + System.out.println(" (CPU 一直忙着轮询,浪费在大量无效的「没到」)");//cpu轮询是因为线程需要不断知道数据是否就绪,于是线程主动调用方法,cpu去判断数据是否就绪。 + System.out.println(" 【关系】1:N(但无效消耗高),一个线程管理N个连接但高频无效检查"); + System.out.println(" 【表现】即使999个连接无数据,线程依然一次次去问,CPU空转"); System.out.println(); System.out.println(" 模型③ IO多路复用 → 外卖平台给你一个取餐器(振动手环),"); System.out.println(" 你回工位安心工作,多个外卖同时等,哪个振了去取哪个。"); System.out.println(" (Selector!1个线程监控N个连接,有就绪才处理)"); + System.out.println(" 【关系】M:N(M< Date: Tue, 26 May 2026 19:43:47 +0800 Subject: [PATCH 04/12] =?UTF-8?q?Psrt2=E8=A1=A5=E5=85=85-=E5=A4=9A?= =?UTF-8?q?=E8=B7=AF=E5=A4=8D=E7=94=A8=E4=B8=89=E7=A7=8D=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../show_multi_agent/Part2_BIOProblem.java | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part2_BIOProblem.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part2_BIOProblem.java index 8b51e7a..049d6e8 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part2_BIOProblem.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part2_BIOProblem.java @@ -25,8 +25,16 @@ static void explain() { System.out.println(" 还要频繁切换谁来工作(上下文切换开销巨大)"); System.out.println(); System.out.println(" 【NIO 模式(对比)】"); - System.out.println(" 一个「大堂经理」(Selector 线程) 管理所有客户取号"); - System.out.println(" 谁的号到了(数据就绪)就叫谁,后台只需少量处理人员"); + System.out.println(" 「大堂经理团队」(Selector + Worker线程池) 管理所有客户取号"); + System.out.println(); + System.out.println(" 简单场景:1个「大堂经理」(单Selector线程) 管理所有客户"); + System.out.println(" → 既负责叫号又办理业务,简单但无法利用多核CPU"); + System.out.println(); + System.out.println(" 生产环境:多个「业务员」(Worker线程池),每人管理一部分客户"); + System.out.println(" → Boss线程接受连接,Worker线程处理读写,可并行处理"); + System.out.println(" → Netty、现代Tomcat都采用这种主从Reactor模式"); + System.out.println(); + System.out.println(" 共同优势:谁的号到了(数据就绪)就叫谁,后台只需少量人员"); System.out.println(" 10个人轻松处理1000个客户!"); System.out.println(); System.out.println("═══ 以下是技术代码分析 ═══"); @@ -62,8 +70,20 @@ static void explain() { System.out.println(" 超过 200 个并发请求 → 排队等待 → 高并发直接崩"); System.out.println(); System.out.println(" 解决方案:IO 多路复用(NIO Selector)"); - System.out.println(" NIO:1个 Selector 线程 + 少量 Worker 线程"); - System.out.println(" → 轻松处理 10 万并发连接"); + System.out.println(" NIO架构:Boss线程(接受连接)+ Worker线程池(处理读写)"); + System.out.println(); + System.out.println(" 模式1【单Reactor单线程】:1个线程既accept又读写"); + System.out.println(" → 简单但无法利用多核,性能最低"); + System.out.println(); + System.out.println(" 模式2【单Reactor多线程】:1个 Boss + 多个 Worker"); + System.out.println(" → Boss接受连接,分发给Worker的Selector"); + System.out.println(" → Netty默认模式,适合大多数场景"); + System.out.println(); + System.out.println(" 模式3【主从Reactor多线程】:多个 Boss + 多个 Worker"); + System.out.println(" → 多个Boss监听不同端口/IP,Accept性能更高"); + System.out.println(" → 适合超高并发(网关、代理服务器)"); + System.out.println(); + System.out.println(" 共同优势:轻松处理 10 万并发连接"); System.out.println(" → 这是 Netty / 现代 Tomcat(NIO 模式)的基础"); System.out.println(); NIODemo.printSeparator(); From 71265319ef0d6fbd835c52ab60605d1f44fd463d Mon Sep 17 00:00:00 2001 From: IE619 <984680432@qq.com> Date: Wed, 27 May 2026 15:57:34 +0800 Subject: [PATCH 05/12] =?UTF-8?q?Part5=E7=9F=A5=E8=AF=86=E7=82=B9=E8=A1=A5?= =?UTF-8?q?=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Part4_BufferStateMachine.java | 43 ++++++++++++++++++- .../show_multi_agent/Part5_ChannelTypes.java | 32 ++++++++------ 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part4_BufferStateMachine.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part4_BufferStateMachine.java index 761762b..1baeebb 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part4_BufferStateMachine.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part4_BufferStateMachine.java @@ -70,6 +70,39 @@ // ==================================================================== class Part4_BufferStateMachine { + // ── 前置知识:GC 与内存管理范围 ─────────────────────────────────────── + // + // GC(Garbage Collection,垃圾回收)只管 JVM 堆内的内存: + // + // 物理内存整体布局: + // ┌────────────────────────────────┐ + // │ 整个物理内存 (RAM) │ + // ├────────────────────────────────┤ + // │ ┌──────────────────────────┐ │ + // │ │ JVM 进程空间 │ │ ← GC 只管理这里 + // │ │ ┌────────────────────┐ │ │ + // │ │ │ JVM 堆 (Heap) │ │ │ ← GC 主要工作区 + // │ │ │ - 对象实例 │ │ │ (会移动对象地址) + // │ │ │ - 数组 │ │ │ + // │ │ └────────────────────┘ │ │ + // │ └──────────────────────────┘ │ + // │ │ + // │ ┌──────────────────────────┐ │ + // │ │ 堆外内存 │ │ ← GC 不管这里 + // │ │ - Direct Buffer │ │ (地址固定不变) + // │ │ - mmap 映射文件 │ │ + // │ │ - JNI native 内存 │ │ + // │ └──────────────────────────┘ │ + // └────────────────────────────────┘ + // + // 关键洞察: + // • Heap Buffer 在 JVM 堆内 → GC 会移动它的地址 → DMA 不能用 + // • Direct Buffer 在堆外 → GC 碰不到 → 地址固定 → DMA 可以直接用 + // • 所以 Heap Buffer IO 需要额外拷贝:堆内 → 堆外临时区 → 内核 + // • Direct Buffer IO 少一次拷贝:堆外Direct → 内核 + // + // ──────────────────────────────────────────────────────────────────── + static void demonstrate() { System.out.println("【第四部分:Buffer 三指针状态机(flip / clear / compact)】"); System.out.println(); @@ -108,6 +141,12 @@ static void demonstrate() { System.out.println(" compact() 做的事:把 C 移到最前面,然后录音头从 C 后面开始"); System.out.println(" 这样下次可以先听 C 再听 D(粘包处理的核心!)"); System.out.println(); + System.out.println(" 💡 关键理解:为什么 AB 被覆盖没关系?"); + System.out.println(" 因为 AB 已经播放完了(已读取并处理),不需要再保留"); + System.out.println(" Buffer 的设计哲学:「读完即弃」—— position 之前的数据都是已处理的垃圾"); + System.out.println(" compact() 只关心保留 [position, limit) 区间的未读数据"); + System.out.println(" 已读数据被覆盖是正确且必要的行为,为后续写入腾出空间"); + System.out.println(); System.out.println("═══ 以下是技术演示(对照上面的比喻来看)═══"); System.out.println(); System.out.println("━━━ ByteBuffer 的内存结构 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); @@ -234,8 +273,8 @@ static void demonstrate() { System.out.println(" │ flip() │ limit=position, position=0 │ 写完数据,准备读取/发送 │"); System.out.println(" │ │ 写模式 → 读模式 │ channel.write(buf)之前必调 │"); System.out.println(" ├───────────┼──────────────────────────────┼──────────────────────────────┤"); - System.out.println(" │ clear() │ position=0, limit=capacity │ 数据已全部读完,复用Buffer │"); - System.out.println(" │ │ 任意 → 写模式(不清数据) │ ⚠ 未读数据会丢失! │"); + System.out.println(" │ clear() │ position=0, limit=capacity │ 重置指针,从头开始写入 │"); + System.out.println(" │ │ 任意 → 写模式(不清数据) │ ⚠ 未读数据会被覆盖! │"); System.out.println(" ├───────────┼──────────────────────────────┼──────────────────────────────┤"); System.out.println(" │ compact() │ 未读数据移到头部 │ 处理粘包/半包,保留未读数据 │"); System.out.println(" │ │ position=剩余量,limit=cap │ 读模式 → 写模式(保留数据) │"); diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part5_ChannelTypes.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part5_ChannelTypes.java index 3abc2e0..f1be530 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part5_ChannelTypes.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part5_ChannelTypes.java @@ -20,6 +20,10 @@ static void explain() { System.out.println(" - 可以跳到指定位置(position(long) 直接跳到文件第N字节)"); System.out.println(" - 必须配合「水桶」(Buffer)使用,不能直接拧开就用"); System.out.println(" - 可以非阻塞(网络Channel),可以注册到Selector统一管理"); + System.out.println(" · 非阻塞:调用read/write时不会等待数据就绪,立即返回结果"); + System.out.println(" · 注册:将Channel注册到Selector,让Selector监控该Channel的事件状态"); + System.out.println(" · 谁注册:开发者通过channel.register(selector, ops)方法进行注册"); + System.out.println(" · 注册什么:将Channel对象与Selector关联,并指定感兴趣的事件类型"); System.out.println(); System.out.println(" Buffer(缓冲区)= 装水的桶:"); System.out.println(" 数据不能直接从 Channel 流向你,"); @@ -28,7 +32,7 @@ static void explain() { System.out.println(" 你的代码 → Buffer → Channel (写)"); System.out.println(); System.out.println(" ★ 关键区别记忆:"); - System.out.println(" Stream = 单向水龙头,直接流,不需要桶"); + System.out.println(" Stream = 单向水龙头,本身无缓冲(可套BufferedInputStream加缓冲)"); System.out.println(" Channel = 双向管道,必须配合桶(Buffer),功能更强大"); System.out.println(); System.out.println("═══ 以下是各种 Channel 类型的详细介绍 ═══"); @@ -46,24 +50,24 @@ static void explain() { System.out.println("Channel 类型体系:"); System.out.println(" Channel(接口)"); System.out.println(" ├── FileChannel 文件读写"); - System.out.println(" │ ├── read(ByteBuffer)"); - System.out.println(" │ ├── write(ByteBuffer)"); - System.out.println(" │ ├── transferTo(pos,n,ch) ← 零拷贝!"); - System.out.println(" │ └── map(mode,pos,size) ← 内存映射"); + System.out.println(" │ ├── read(ByteBuffer) 从Channel读取数据到Buffer"); + System.out.println(" │ ├── write(ByteBuffer) 从Buffer写入数据到Channel"); + System.out.println(" │ ├── transferTo(pos,n,ch) 零拷贝传输数据到另一个Channel"); + System.out.println(" │ └── map(mode,pos,size) 将文件区域映射到内存"); System.out.println(" │ ⚠️ 不支持非阻塞!不能注册 Selector"); System.out.println(" │"); System.out.println(" ├── ServerSocketChannel 监听端口(接受连接)"); - System.out.println(" │ ├── bind(address)"); - System.out.println(" │ ├── accept() → SocketChannel"); - System.out.println(" │ └── register(sel, OP_ACCEPT)"); + System.out.println(" │ ├── bind(address) 绑定到指定端口地址"); + System.out.println(" │ ├── accept() → SocketChannel 接受新连接,返回SocketChannel"); + System.out.println(" │ └── register(sel, OP_ACCEPT) 注册到Selector,监听接受连接事件"); System.out.println(" │"); System.out.println(" ├── SocketChannel TCP 连接"); - System.out.println(" │ ├── connect(address)"); - System.out.println(" │ ├── read(ByteBuffer)"); - System.out.println(" │ ├── write(ByteBuffer)"); - System.out.println(" │ └── register(sel, OP_READ | OP_WRITE)"); + System.out.println(" │ ├── connect(address) 连接到指定服务器地址"); + System.out.println(" │ ├── read(ByteBuffer) 从Channel读取数据到Buffer"); + System.out.println(" │ ├── write(ByteBuffer) 从Buffer写入数据到Channel"); + System.out.println(" │ └── register(sel, OP_READ | OP_WRITE) 注册到Selector,监听读写事件"); System.out.println(" │"); - System.out.println(" └── DatagramChannel UDP"); + System.out.println(" └── DatagramChannel UDP 无连接数据报通信"); System.out.println(); System.out.println("★ FileChannel 为什么不能非阻塞?"); System.out.println(" Linux 内核设计:普通文件 fd 对 epoll「永远是就绪的」"); @@ -78,7 +82,7 @@ static void explain() { System.out.println(" FileChannel fc = FileChannel.open(path, StandardOpenOption.READ);"); System.out.println(" // 传统 IO 流获取"); System.out.println(" FileChannel fc = new FileInputStream(file).getChannel();"); - System.out.println(" // 注意:关闭 Channel 不会自动关闭 Stream,反之亦然"); + System.out.println(" // 注意:关闭 Channel 不会自动关闭 Stream,反之亦然");//需要分别关闭 System.out.println(); NIODemo.printSeparator(); } From 242e6234f018d59345e53411de19564b1bd04016 Mon Sep 17 00:00:00 2001 From: IE619 <984680432@qq.com> Date: Wed, 27 May 2026 16:15:22 +0800 Subject: [PATCH 06/12] =?UTF-8?q?Part6-select()=E7=9A=84epoll=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E6=96=B9=E5=BC=8F=E6=98=AF=E4=B8=8D=E6=98=AF=E8=BF=99?= =?UTF-8?q?=E6=A0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/nio/show_multi_agent/Part6_SelectorEpoll.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part6_SelectorEpoll.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part6_SelectorEpoll.java index 2817380..b2f7491 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part6_SelectorEpoll.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part6_SelectorEpoll.java @@ -40,13 +40,13 @@ static void explain() { System.out.println(" ↓"); System.out.println(" JVM 调用 Linux epoll_wait(epfd, events, maxevents, timeout)"); System.out.println(" ↓"); - System.out.println(" 内核检查所有注册 fd,没有就绪 → 线程挂起(进等待队列)"); + System.out.println(" 内核先检查就绪链表:有就绪fd→立即返回;无就绪→线程挂起等待"); System.out.println(" ↓"); System.out.println(" CPU 去干别的事(这就是单线程能管N个连接的关键!)"); System.out.println(" ↓"); - System.out.println(" 某个 fd 就绪(网卡收到数据 → 硬件中断触发)"); + System.out.println(" 某个 fd 就绪(网卡收到数据 → 硬件中断 → 回调函数执行)"); System.out.println(" ↓"); - System.out.println(" 内核唤醒线程,返回就绪 fd 列表"); + System.out.println(" 内核将该 fd 加入就绪链表,唤醒等待的线程"); System.out.println(" ↓"); System.out.println(" selector.select() 返回,值 = 就绪 fd 数量"); System.out.println(); From ffffec85bdca858fbd4d1692ee4070884ed6f83a Mon Sep 17 00:00:00 2001 From: IE619 <984680432@qq.com> Date: Wed, 27 May 2026 18:35:37 +0800 Subject: [PATCH 07/12] =?UTF-8?q?Part6-=E6=95=B0=E6=8D=AE=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/nio/show_multi_agent/Part7_SelectionKeyEvents.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part7_SelectionKeyEvents.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part7_SelectionKeyEvents.java index 92c654e..f5bc1d9 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part7_SelectionKeyEvents.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part7_SelectionKeyEvents.java @@ -36,11 +36,11 @@ static void explain() { System.out.println(); System.out.println("═══ 以下是技术细节 ═══"); System.out.println(); - System.out.println(" OP_ACCEPT = 16 ServerSocketChannel 专用"); + System.out.println(" OP_ACCEPT = 8 ServerSocketChannel 专用"); System.out.println(" 触发时机:TCP 三次握手完成,有新连接到来"); System.out.println(" 响应:serverChannel.accept() 得到 SocketChannel"); System.out.println(); - System.out.println(" OP_CONNECT = 8 客户端 SocketChannel 专用"); + System.out.println(" OP_CONNECT = 4 客户端 SocketChannel 专用"); System.out.println(" 触发时机:非阻塞 connect() 的握手完成"); System.out.println(" 响应:必须调用 channel.finishConnect()"); System.out.println(" 不调用后果:channel 不可用,后续 read/write 报错"); @@ -51,7 +51,7 @@ static void explain() { System.out.println(" ⚠️ 返回 -1 = 对端关闭,必须 key.cancel() + channel.close()"); System.out.println(" ⚠️ 返回 0 = 非阻塞模式下没数据,稍后再来"); System.out.println(); - System.out.println(" OP_WRITE = 4 最容易误用!"); + System.out.println(" OP_WRITE = 2 最容易误用!"); System.out.println(" 含义:Socket 发送缓冲区有空间,可以写数据"); System.out.println(); System.out.println(" ⚠️ 陷阱:发送缓冲区「几乎一直有空间」"); From 35d1ca8f8743f97a906f0a19b9c100d7a6d2640e Mon Sep 17 00:00:00 2001 From: IE619 <984680432@qq.com> Date: Thu, 28 May 2026 17:50:44 +0800 Subject: [PATCH 08/12] =?UTF-8?q?Part8-=E7=BB=86=E8=8A=82=E8=A1=A5?= =?UTF-8?q?=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/nio/show_multi_agent/Part8_NIOServerExplained.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part8_NIOServerExplained.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part8_NIOServerExplained.java index b949084..d7bba97 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part8_NIOServerExplained.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part8_NIOServerExplained.java @@ -119,12 +119,19 @@ static void explain() { System.out.println(); System.out.println("Netty 解决的原生 NIO 七宗罪:"); System.out.println(" ① Buffer flip/clear 容易搞错 → ByteBuf 双指针,无需 flip"); + System.out.println(" 原生NIO需手动切换读写模式,易遗漏;ByteBuf自动管理readerIndex/writerIndex"); System.out.println(" ② 粘包/拆包需自己处理 → 内置多种 FrameDecoder"); + System.out.println(" TCP流式协议无消息边界;Netty提供长度/分隔符等解码器自动处理"); System.out.println(" ③ JDK Selector epoll 空轮询 Bug→ 检测重建 Selector"); + System.out.println(" Linux下select()可能立即返回导致CPU 100%;Netty检测后自动重建"); System.out.println(" ④ 异常处理繁琐 → Pipeline 统一处理"); + System.out.println(" 原生NIO到处try-catch;Netty通过责任链在末尾统一捕获异常"); System.out.println(" ⑤ 无连接池 → Channel 对象池"); + System.out.println(" 频繁创建销毁Channel开销大;Netty提供ChannelPool复用连接"); System.out.println(" ⑥ 无编解码 → 内置 HTTP/WebSocket/自定义"); + System.out.println(" 原生NIO只传字节;Netty内置HTTP/Protobuf/JSON等编解码器"); System.out.println(" ⑦ 无心跳机制 → IdleStateHandler"); + System.out.println(" 需手动实现心跳检测;Netty可配置空闲超时自动触发事件"); System.out.println(); NIODemo.printSeparator(); } From 0cc24aa37d721196e7c31b277809783fe76b64fa Mon Sep 17 00:00:00 2001 From: IE619 <984680432@qq.com> Date: Sat, 30 May 2026 14:16:36 +0800 Subject: [PATCH 09/12] =?UTF-8?q?Part13-=E5=8E=9F=E5=9B=A0=E8=A1=A5?= =?UTF-8?q?=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Part13_MistakesAndSelection.java | 42 ++++++++++++------- .../show_multi_agent/Part1_FiveIOModels.java | 2 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part13_MistakesAndSelection.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part13_MistakesAndSelection.java index a33ff45..dd7e48c 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part13_MistakesAndSelection.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part13_MistakesAndSelection.java @@ -33,30 +33,44 @@ static void explain() { System.out.println(" 真相:连接数少(<100)时,BIO 代码更简单,性能差不多"); System.out.println(" 连接数多(>1000)时,NIO 才有明显优势"); System.out.println(" 本质:BIO 瓶颈是线程数,NIO 瓶颈是 CPU 处理能力"); + System.out.println(" 小项目用 NIO 是过度设计,代码复杂但收益不大"); System.out.println(); System.out.println(" 误区2:以为 selector.select() 是忙等(CPU spin)"); System.out.println(" 真相:是真正的线程休眠(epoll_wait),该线程不占用 CPU 时间片"); + System.out.println(" 内核有事件时才唤醒线程,属于事件驱动,不是轮询"); System.out.println(); System.out.println(" 误区3:OP_WRITE 一直注册"); System.out.println(" 真相:发送缓冲区几乎一直有空间,OP_WRITE 几乎一直就绪"); System.out.println(" select() 一直立刻返回 → CPU 100%!"); System.out.println(" 正确:只在写不完时注册,写完立刻取消"); + System.out.println(" 类比:一直问“能发货吗”,快递员被你烦死了"); System.out.println(); System.out.println(" 误区4:selectedKeys() 不 remove()"); System.out.println(" 真相:已处理的 key 不自动移除,下次 select() 还会返回"); - System.out.println(" → 同一事件重复处理"); + System.out.println(" → 同一事件重复处理,数据错乱或重复发送"); + System.out.println(" 必须手动 iterator.remove(),否则逻辑混乱"); System.out.println(); System.out.println(" 误区5:FileChannel 以为能注册 Selector"); System.out.println(" 真相:FileChannel 不支持非阻塞模式,不能注册 Selector"); + System.out.println(" 磁盘 IO 是同步的,没有“就绪”概念,只能用阻塞方式读写"); System.out.println(); System.out.println(" 误区6:transferTo 不写 while 循环"); System.out.println(" 真相:网络场景一次可能传不完,不写 while 会静默丢数据"); + System.out.println(" 本地文件传输一般一次搞定,但网络传输必须循环检查"); + System.out.println(" 直到 transferred == count 才算完成"); System.out.println(); System.out.println(" 误区7:Netty handler 里做阻塞操作(数据库查询)"); System.out.println(" 真相:会把 EventLoop 线程卡死,整个服务停响"); System.out.println(" 解决:ctx.executor().execute(() → { 阻塞操作 → 回写 })"); System.out.println(" 或用 eventLoop.submit() 提交到业务线程池"); System.out.println(); + System.out.println(" ★ EventLoop 本质:一个线程 + 一个 Selector + 管理 N 个 Channel"); + System.out.println(" Netty 采用 Reactor 主从模式,Boss 线程负责 accept,Worker 线程负责读写"); + System.out.println(" 每个 Channel 绑定一个 EventLoop,同一 Channel 的事件始终在同一线程处理"); + System.out.println(" → 无锁化设计,无需 synchronized,这是高并发的关键"); + System.out.println(" → 一旦 handler 阻塞,该 EventLoop 负责的所有 Channel 都无法响应"); + System.out.println(" → 相当于一个快递员被绑在厕所,整片小区没人送快递"); + System.out.println(); System.out.println("二、CPU 拷贝 vs DMA 拷贝(容易混淆)"); System.out.println(); @@ -81,19 +95,19 @@ static void explain() { System.out.println("三、技术选型指南"); System.out.println(); - System.out.println(" ┌─────────────────────────┬────────────────────────────────────┐"); - System.out.println(" │ 场景 │ 推荐方案 │"); - System.out.println(" ├─────────────────────────┼────────────────────────────────────┤"); - System.out.println(" │ 读写本地小文件 │ BufferedInputStream/OutputStream │"); - System.out.println(" │ │ 或 Files.readAllBytes(< 64MB) │"); - System.out.println(" │ 大文件随机读取(GB级) │ MappedByteBuffer(内存映射) │"); - System.out.println(" │ 本地文件传输到网络 │ FileChannel.transferTo(零拷贝) │"); - System.out.println(" │ 自己写 HTTP/TCP 服务 │ Netty(不要用原生NIO,太复杂) │"); - System.out.println(" │ 微服务 RPC │ Dubbo/gRPC(底层都是 Netty) │"); - System.out.println(" │ 消息队列 │ Kafka(零拷贝+顺序写) │"); - System.out.println(" │ 高并发 Web 静态资源 │ Nginx(sendfile) │"); - System.out.println(" │ 高并发 Web 动态接口 │ Spring Boot(底层 Netty/Undertow) │"); - System.out.println(" └─────────────────────────┴────────────────────────────────────┘"); + System.out.println(" ┌─────────────────────────┬────────────────────────────────────┬─────────────────────────────────────┐"); + System.out.println(" │ 场景 │ 推荐方案 │ 原因 │"); + System.out.println(" ├─────────────────────────┼────────────────────────────────────┼─────────────────────────────────────┤"); + System.out.println(" │ 读写本地小文件 │ BufferedInputStream/OutputStream │ Buffer 缓冲够用,代码简单性能好 │"); + System.out.println(" │ │ 或 Files.readAllBytes(< 64MB) │ │"); + System.out.println(" │ 大文件随机读取(GB级) │ MappedByteBuffer(内存映射) │ mmap 映射到内存,随机访问像数组一样快 │"); + System.out.println(" │ 本地文件传输到网络 │ FileChannel.transferTo(零拷贝) │ 数据直接从 Page Cache 通过 DMA 发出 │"); + System.out.println(" │ 自己写 HTTP/TCP 服务 │ Netty(不要用原生NIO,太复杂) │ 原生 NIO 七大坑,Netty 全部封装好了 │"); + System.out.println(" │ 微服务 RPC │ Dubbo/gRPC(底层都是 Netty) │ 序列化、服务发现、负载均衡都内置了 │"); + System.out.println(" │ 消息队列 │ Kafka(零拷贝+顺序写) │ 顺序写 + sendfile,吞吐量 GB/s 级别 │"); + System.out.println(" │ 高并发 Web 静态资源 │ Nginx(sendfile) │ 静态文件直接从磁盘 DMA 到网卡 │"); + System.out.println(" │ 高并发 Web 动态接口 │ Spring Boot(底层 Netty/Undertow) │ Netty 处理连接 + Spring 开发效率高 │"); + System.out.println(" └─────────────────────────┴────────────────────────────────────┴─────────────────────────────────────┘"); System.out.println(); System.out.println("四、整体知识体系一张图"); diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part1_FiveIOModels.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part1_FiveIOModels.java index 55033a1..6a6959b 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part1_FiveIOModels.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part1_FiveIOModels.java @@ -43,7 +43,7 @@ static void explain() { System.out.println(" 外卖放桌上了才通知你(两阶段都是OS帮你完成)"); System.out.println(" 【关系】完全解耦,线程发起aio_read后立即返回"); System.out.println(" 【特点】整个IO由操作系统完成,完成后通过回调通知线程"); - System.out.println(" 【现状】Linux原生AIO在网络IO支持不成熟,仍以多路复用为主流"); + System.out.println(" 【现状】Linux原生AIO在网络IO支持不成熟,仍以多路复用为主流");//111 System.out.println(); System.out.println("═══ 以上就是5种IO模型的核心区别,下面是技术细节 ═══"); System.out.println(); From f1849754a7a3c726b8f6236be0d06bbbd4be3805 Mon Sep 17 00:00:00 2001 From: IE619 <984680432@qq.com> Date: Thu, 4 Jun 2026 15:39:16 +0800 Subject: [PATCH 10/12] =?UTF-8?q?Part17-=E5=86=85=E5=AE=B9=E8=A1=A5?= =?UTF-8?q?=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Part14_FileChannelLocalIO.java | 28 ++++++- .../Part16_NIO2FilesAndWatch.java | 12 +-- .../Part17_NettyArchitecture.java | 75 ++++++++++++++----- .../show_multi_agent/Part2_BIOProblem.java | 2 +- .../Part3_BufferedInputStreamSpeedup.java | 18 +++-- .../Part7_SelectionKeyEvents.java | 6 +- 6 files changed, 104 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part14_FileChannelLocalIO.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part14_FileChannelLocalIO.java index 94fc3bf..2a6f282 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part14_FileChannelLocalIO.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part14_FileChannelLocalIO.java @@ -1,8 +1,24 @@ package org.example.java_base_test.io.nio.show_multi_agent; import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.nio.file.Files; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.RandomAccessFile; +import java.nio.channels.SeekableByteChannel; +import java.util.Set; +import java.nio.file.attribute.PosixFilePermission; -class Part14_FileChannelLocalIO { +public class Part14_FileChannelLocalIO { + + public static void main(String[] args) throws Exception { + demonstrate(); + } static void demonstrate() throws Exception { System.out.println("【第十四部分:FileChannel 本地文件 IO 完整操作】"); @@ -55,6 +71,12 @@ static void demonstrate() throws Exception { System.out.println(" FileChannel writeCh = new FileOutputStream(file).getChannel();"); System.out.println(" FileChannel rwCh = new RandomAccessFile(file, \"rw\").getChannel();"); System.out.println(); + System.out.println(" // 方式三:使用 Files.newByteChannel() 返回 SeekableByteChannel"); + System.out.println(" SeekableByteChannel sbc = Files.newByteChannel(path, options);"); + System.out.println(" if (sbc instanceof FileChannel) {"); + System.out.println(" FileChannel fc = (FileChannel) sbc;"); + System.out.println(" }"); + System.out.println(); System.out.println(" ⚠ 注意:关闭 Channel 会自动关闭关联的 Stream,反之亦然"); System.out.println(" Stream 和 Channel 不要各自关,关一个就够了"); System.out.println(); @@ -110,7 +132,7 @@ static void demonstrate() throws Exception { java.nio.file.Path tmpFile2 = java.nio.file.Files.createTempFile("fc_pos_", ".txt"); try { - // 先写 "ABCDEFGHIJ"(10字节) + // 先写 "ABCDE FGHIJ"(10字节) try (java.nio.channels.FileChannel fc = java.nio.channels.FileChannel.open(tmpFile2, java.nio.file.StandardOpenOption.WRITE, @@ -203,7 +225,7 @@ static void demonstrate() throws Exception { System.out.println(" 场景:两个 JVM 进程同时写同一个文件 → 数据损坏"); System.out.println(" FileLock 是操作系统级别的锁,不同进程都能感知"); System.out.println(" 注意:同一个 JVM 内多线程用 FileLock 无效,要用 synchronized"); - System.out.println(); + System.out.println( ); System.out.println(" 两种锁:"); System.out.println(" 独占锁(写锁):fc.lock() → 其他进程的 lock() 会阻塞"); System.out.println(" 共享锁(读锁):fc.lock(0, Long.MAX_VALUE, true)"); diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part16_NIO2FilesAndWatch.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part16_NIO2FilesAndWatch.java index 635f5bc..6327192 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part16_NIO2FilesAndWatch.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part16_NIO2FilesAndWatch.java @@ -36,12 +36,12 @@ static void demonstrate() throws Exception { java.nio.file.Path p = java.nio.file.Paths.get("/Users/demo/data/test.txt"); System.out.println(" Path p = Paths.get(\"/Users/demo/data/test.txt\")"); - System.out.println(" p.getFileName() = " + p.getFileName()); - System.out.println(" p.getParent() = " + p.getParent()); - System.out.println(" p.getRoot() = " + p.getRoot()); - System.out.println(" p.getNameCount() = " + p.getNameCount() + " (路径分量数)"); - System.out.println(" p.getName(1) = " + p.getName(1) + " (第2段路径)"); - System.out.println(" p.isAbsolute() = " + p.isAbsolute()); + System.out.println(" p.getFileName() = " + p.getFileName()); // 获取文件名:test.txt(最后一段) + System.out.println(" p.getParent() = " + p.getParent()); // 获取父目录:/Users/demo/data + System.out.println(" p.getRoot() = " + p.getRoot()); // 获取根目录:/(Unix系统) + System.out.println(" p.getNameCount() = " + p.getNameCount() + " (路径分量数)"); // 3个分量:Users、data、test.txt + System.out.println(" p.getName(1) = " + p.getName(1) + " (第2段路径)"); // 索引从0开始:0=Users, 1=data, 2=test.txt + System.out.println(" p.isAbsolute() = " + p.isAbsolute()); // true:以 / 开头的是绝对路径 System.out.println(); // resolve:拼接路径 diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part17_NettyArchitecture.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part17_NettyArchitecture.java index 8b0f72d..33044fa 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part17_NettyArchitecture.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part17_NettyArchitecture.java @@ -55,6 +55,36 @@ static void explain() { System.out.println(" │ ⑦ │ 无心跳机制 │ IdleStateHandler │"); System.out.println(" └────┴─────────────────────────────┴──────────────────────────────┘"); System.out.println(); + System.out.println(" 【各痛点详解】"); + System.out.println(); + System.out.println(" ① ByteBuffer flip/clear 易错"); + System.out.println(" 原因:读写共用 1 个 position 指针,写完必须 flip() 才能读,忘了就读到空数据"); + System.out.println(" 解决:ByteBuf 用 readerIndex + writerIndex 双指针,读写独立,永远不需要 flip"); + System.out.println(); + System.out.println(" ② 粘包/拆包需自己处理"); + System.out.println(" 原因:TCP 是流式协议,无消息边界,多个包可能粘在一起或一个包被拆成多段"); + System.out.println(" 解决:内置 LineBasedFrameDecoder/LengthFieldBasedFrameDecoder 等按规则拆包"); + System.out.println(); + System.out.println(" ③ JDK epoll 空轮询 Bug"); + System.out.println(" 原因:Linux epoll 在连接异常关闭时,select() 无事件也立即返回,死循环 CPU 100%"); + System.out.println(" 解决:Netty 检测连续空轮询超 512 次,自动销毁旧 Selector 并重建新的"); + System.out.println(); + System.out.println(" ④ 异常处理分散繁琐"); + System.out.println(" 原因:每个 read()/write()/accept() 都要 try-catch,异常处理散落各处易遗漏"); + System.out.println(" 解决:Pipeline 中任何 Handler 抛异常自动传播到 exceptionCaught() 统一兜底"); + System.out.println(); + System.out.println(" ⑤ 无连接池"); + System.out.println(" 原因:原生 NIO 每次连接都创建新对象,频繁创建/销毁造成大量 GC 开销"); + System.out.println(" 解决:Netty 内部用 Recycler 对象池,Channel/ByteBuf 等核心对象可复用"); + System.out.println(); + System.out.println(" ⑥ 无编解码"); + System.out.println(" 原因:原生 NIO 只给原始 byte[],HTTP/WebSocket 等协议解析都要从零手写"); + System.out.println(" 解决:内置 HttpServerCodec/WebSocketHandler/ProtobufDecoder 等开箱即用"); + System.out.println(); + System.out.println(" ⑦ 无心跳机制"); + System.out.println(" 原因:TCP 连接可能「假死」(拔网线/对方宕机),应用层无感知,死连接占资源"); + System.out.println(" 解决:IdleStateHandler 一行配置读/写/读写空闲超时,触发心跳包或断连"); + System.out.println(); System.out.println(" 类比:原生 NIO 是「毛坯房 + 一堆砖头」,Netty 是「精装修公寓」"); System.out.println(" 你进去就可以住(写业务),不用自己砌墙(处理底层细节)"); System.out.println(); @@ -209,6 +239,10 @@ static void explain() { System.out.println(" Unpooled.xxx() 非池化,用完就丢,GC 回收"); System.out.println(" PooledByteBufAllocator.DEFAULT.buffer() 池化,借出用完还回去"); System.out.println(" Netty 默认:堆外 + 池化(高并发下大幅减少 GC 压力)"); + System.out.println(" 堆外原理:GC 通过引用链追踪对象,堆外让引用指向小对象(几十字节)"); + System.out.println(" 而非大数组(几MB),标记/回收工作量降低百倍,暂停时间从秒级降至毫秒级"); + System.out.println(" 池化好处:预先分配大块内存切分复用,避免频繁系统调用(allocateDirect 极慢)"); + System.out.println(" 对象复用不产生垃圾,减少 GC 频率,分配速度从微秒级提升至纳秒级"); System.out.println(); System.out.println(" ⚠ 内存泄漏陷阱:ByteBuf 是引用计数的(refCnt)"); System.out.println(" 创建时 refCnt = 1,调用 release() → refCnt - 1 → 归零则回收"); @@ -222,28 +256,35 @@ static void explain() { // ════════════════════════════════════════════════════════════════ System.out.println("━━━ 5. 从「客户端发数据」到「Handler 收到」的完整流程 ━━━━━━━"); System.out.println(); - System.out.println(" Step1:客户端发送数据包"); - System.out.println(" 网卡收到数据 → DMA 写入 Socket 接收缓冲区"); - System.out.println(" 硬件中断 → 内核唤醒 epoll_wait → Selector.select() 返回"); + System.out.println(" 📦 生活类比:把 Netty 处理请求想象成「餐厅接待顾客」"); + System.out.println(); + System.out.println(" Step1:顾客进店(客户端发送数据包)"); + System.out.println(" 顾客走到门口 → 门铃响起(网卡收到数据)"); + System.out.println(" 服务员听到铃声(硬件中断),注意到有新顾客(Selector.select() 返回)"); System.out.println(); - System.out.println(" Step2:EventLoop 线程处理 OP_READ 事件"); - System.out.println(" channel.read(ByteBuf) ← 把数据从 Socket 缓冲区搬到 ByteBuf"); - System.out.println(" 触发 pipeline.fireChannelRead(ByteBuf)"); + System.out.println(" Step2:接待员迎接(EventLoop 处理 OP_READ 事件)"); + System.out.println(" 接待员把顾客带到等候区(channel.read → 数据从 Socket 搬到 ByteBuf)"); + System.out.println(" 喊一声:"有客人来了!"(触发 pipeline.fireChannelRead)"); System.out.println(); - System.out.println(" Step3:ByteBuf 流过 Pipeline(Inbound 方向)"); - System.out.println(" [Head] → [LengthFieldDecoder] → [ProtobufDecoder] → [BusinessHandler]"); - System.out.println(" 每个 Handler 处理完调用 ctx.fireChannelRead(msg) 传给下一个"); + System.out.println(" Step3:流水线处理(ByteBuf 流过 Pipeline - Inbound 方向)"); + System.out.println(" [接待员] → [翻译员] → [厨师] → [经理]"); + System.out.println(" 接待员:记录顾客需求(LengthFieldDecoder 拆包)"); + System.out.println(" 翻译员:把方言翻译成普通话(ProtobufDecoder 解码)"); + System.out.println(" 厨师:做菜(BusinessHandler 业务处理)"); + System.out.println(" 每个环节做完,交给下一环节(ctx.fireChannelRead 传给下一个)"); System.out.println(); - System.out.println(" Step4:BusinessHandler 处理业务,写响应"); - System.out.println(" ctx.writeAndFlush(response) ← 触发 Outbound 方向"); + System.out.println(" Step4:经理准备回应(BusinessHandler 写响应)"); + System.out.println(" 经理说:"菜做好了,打包给顾客"(ctx.writeAndFlush)"); System.out.println(); - System.out.println(" Step5:响应流过 Pipeline(Outbound 方向)"); - System.out.println(" [BusinessHandler] → [ProtobufEncoder] → [LengthPrepender] → [Head]"); - System.out.println(" Head 将数据写入 Socket 发送缓冲区,网卡 DMA 发出"); + System.out.println(" Step5:打包送出(响应流过 Pipeline - Outbound 方向)"); + System.out.println(" [经理] → [打包员] → [贴标签员] → [外卖员]"); + System.out.println(" 打包员:装盒(ProtobufEncoder 编码)"); + System.out.println(" 贴标签员:写上地址(LengthPrepender 添加长度)"); + System.out.println(" 外卖员:送到顾客手上(Head 写入 Socket,网卡发出)"); System.out.println(); - System.out.println(" 整条链:"); - System.out.println(" 网卡 → Socket缓冲区 → ByteBuf → Pipeline(decode) → Handler"); - System.out.println(" Handler → Pipeline(encode) → Socket缓冲区 → 网卡 → 客户端"); + System.out.println(" 整条链总结:"); + System.out.println(" 顾客 → 门口 → 等候区 → 翻译 → 做菜 → 经理"); + System.out.println(" 经理 → 打包 → 贴标签 → 外卖员 → 顾客"); System.out.println(); NIODemo.printSeparator(); } diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part2_BIOProblem.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part2_BIOProblem.java index 049d6e8..5f66e0a 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part2_BIOProblem.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part2_BIOProblem.java @@ -85,7 +85,7 @@ static void explain() { System.out.println(); System.out.println(" 共同优势:轻松处理 10 万并发连接"); System.out.println(" → 这是 Netty / 现代 Tomcat(NIO 模式)的基础"); - System.out.println(); + System.out.println(); NIODemo.printSeparator(); } } diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part3_BufferedInputStreamSpeedup.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part3_BufferedInputStreamSpeedup.java index d25806c..97db62d 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part3_BufferedInputStreamSpeedup.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part3_BufferedInputStreamSpeedup.java @@ -7,6 +7,10 @@ class Part3_BufferedInputStreamSpeedup { + public static void main(String[] args) throws Exception { + explain(); + } + static void explain() throws Exception { System.out.println("【第三部分:BufferedInputStream 为什么能大幅提速】"); System.out.println(); @@ -59,7 +63,7 @@ static void explain() throws Exception { System.out.println(); // 实际演示 - System.out.println("实际性能演示(4MB 文件,8KB buffer):"); + System.out.println("实际性能演示(4MB 文件):"); Path tmpFile = Files.createTempFile("bio_test_", ".dat"); byte[] data = new byte[4 * 1024 * 1024]; for (int i = 0; i < data.length; i++) data[i] = (byte)(i % 256); @@ -75,16 +79,16 @@ static void explain() throws Exception { start = System.currentTimeMillis(); try (FileInputStream fis = new FileInputStream(tmpFile.toFile())) { - byte[] buf = new byte[8192]; - while (fis.read(buf) != -1) {} + // 逐字节读取,模拟最坏情况 + while (fis.read() != -1) {} } long withoutBuf = System.currentTimeMillis() - start; - System.out.println(" BufferedInputStream:" + withBuf + "ms"); - System.out.println(" FileInputStream 直接读(同 8KB 数组):" + withoutBuf + "ms"); - System.out.println(" (差距在字节级 read() 时更明显;大数组读时 JVM 内部也有缓冲优化)"); + System.out.println(" BufferedInputStream(8KB缓冲 + 8KB数组读):" + withBuf + "ms"); + System.out.println(" FileInputStream 直接读(逐字节 read()):" + withoutBuf + "ms"); + System.out.println(" 性能提升:" + (withoutBuf / Math.max(withBuf, 1)) + " 倍"); System.out.println(" ★ 结论:永远给 FileInputStream 套 BufferedInputStream"); - System.out.println(" 除非你自己传入大的 byte[] 数组"); + System.out.println(" 尤其是当你需要逐字节或小字节读取时,差距可达数千倍!"); Files.deleteIfExists(tmpFile); System.out.println(); NIODemo.printSeparator(); diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part7_SelectionKeyEvents.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part7_SelectionKeyEvents.java index f5bc1d9..92c654e 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part7_SelectionKeyEvents.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part7_SelectionKeyEvents.java @@ -36,11 +36,11 @@ static void explain() { System.out.println(); System.out.println("═══ 以下是技术细节 ═══"); System.out.println(); - System.out.println(" OP_ACCEPT = 8 ServerSocketChannel 专用"); + System.out.println(" OP_ACCEPT = 16 ServerSocketChannel 专用"); System.out.println(" 触发时机:TCP 三次握手完成,有新连接到来"); System.out.println(" 响应:serverChannel.accept() 得到 SocketChannel"); System.out.println(); - System.out.println(" OP_CONNECT = 4 客户端 SocketChannel 专用"); + System.out.println(" OP_CONNECT = 8 客户端 SocketChannel 专用"); System.out.println(" 触发时机:非阻塞 connect() 的握手完成"); System.out.println(" 响应:必须调用 channel.finishConnect()"); System.out.println(" 不调用后果:channel 不可用,后续 read/write 报错"); @@ -51,7 +51,7 @@ static void explain() { System.out.println(" ⚠️ 返回 -1 = 对端关闭,必须 key.cancel() + channel.close()"); System.out.println(" ⚠️ 返回 0 = 非阻塞模式下没数据,稍后再来"); System.out.println(); - System.out.println(" OP_WRITE = 2 最容易误用!"); + System.out.println(" OP_WRITE = 4 最容易误用!"); System.out.println(" 含义:Socket 发送缓冲区有空间,可以写数据"); System.out.println(); System.out.println(" ⚠️ 陷阱:发送缓冲区「几乎一直有空间」"); From ecc0cafc0c0f00955b465beb08a1c24bb5f792eb Mon Sep 17 00:00:00 2001 From: IE619 <984680432@qq.com> Date: Thu, 4 Jun 2026 20:53:47 +0800 Subject: [PATCH 11/12] =?UTF-8?q?Part18-=E5=86=85=E5=AE=B9=E8=A1=A5?= =?UTF-8?q?=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Part17_NettyArchitecture.java | 4 +- .../Part18_NettyFrameDecoder.java | 63 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part17_NettyArchitecture.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part17_NettyArchitecture.java index 33044fa..8ffbdb6 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part17_NettyArchitecture.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part17_NettyArchitecture.java @@ -264,7 +264,7 @@ static void explain() { System.out.println(); System.out.println(" Step2:接待员迎接(EventLoop 处理 OP_READ 事件)"); System.out.println(" 接待员把顾客带到等候区(channel.read → 数据从 Socket 搬到 ByteBuf)"); - System.out.println(" 喊一声:"有客人来了!"(触发 pipeline.fireChannelRead)"); + System.out.println(" 喊一声:'有客人来了!'(触发 pipeline.fireChannelRead)"); System.out.println(); System.out.println(" Step3:流水线处理(ByteBuf 流过 Pipeline - Inbound 方向)"); System.out.println(" [接待员] → [翻译员] → [厨师] → [经理]"); @@ -274,7 +274,7 @@ static void explain() { System.out.println(" 每个环节做完,交给下一环节(ctx.fireChannelRead 传给下一个)"); System.out.println(); System.out.println(" Step4:经理准备回应(BusinessHandler 写响应)"); - System.out.println(" 经理说:"菜做好了,打包给顾客"(ctx.writeAndFlush)"); + System.out.println(" 经理说:'菜做好了,打包给顾客'(ctx.writeAndFlush)"); System.out.println(); System.out.println(" Step5:打包送出(响应流过 Pipeline - Outbound 方向)"); System.out.println(" [经理] → [打包员] → [贴标签员] → [外卖员]"); diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part18_NettyFrameDecoder.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part18_NettyFrameDecoder.java index 7372ad7..2b2ef73 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part18_NettyFrameDecoder.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part18_NettyFrameDecoder.java @@ -198,6 +198,69 @@ static void explain() { System.out.println(" }"); System.out.println(); NIODemo.printSeparator(); + + // ════════════════════════════════════════════════════════════════ + // 第五节:各方案特点对比总结 + // ════════════════════════════════════════════════════════════════ + System.out.println("━━━ 5. 各消息边界方案特点对比总结 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + System.out.println(); + System.out.println(" 【方案对比表】"); + System.out.println(" ┌────────────┬──────────┬──────────┬──────────┬───────────┐"); + System.out.println(" │ 方案 │ 变长支持 │ 性能 │ 复杂度 │ 适用场景 │"); + System.out.println(" ├────────────┼──────────┼──────────┼──────────┼───────────┤"); + System.out.println(" │ 固定长度 │ ❌ │ ⭐⭐⭐⭐⭐│ 极简 │ 定长协议 │"); + System.out.println(" │ 分隔符 │ ✅ │ ⭐⭐⭐ │ 简单 │ 文本协议 │"); + System.out.println(" │ 长度字段 │ ✅ │ ⭐⭐⭐⭐⭐│ 中等 │ 二进制协议│"); + System.out.println(" │ 自定义协议 │ ✅ │ ⭐⭐⭐⭐ │ 复杂 │ RPC框架 │"); + System.out.println(" └────────────┴──────────┴──────────┴──────────┴───────────┘"); + System.out.println(); + System.out.println(" 【详细对比】"); + System.out.println(); + System.out.println(" 1️⃣ 固定长度(FixedLengthFrameDecoder)"); + System.out.println(" 核心特点:每条消息必须严格等于 N 字节"); + System.out.println(" 优点:实现最简单,解码速度最快(无需解析头部)"); + System.out.println(" 缺点:消息短则浪费空间(需填充),消息长则被截断"); + System.out.println(" 典型应用:传感器数据上报、硬件通信协议"); + System.out.println(" 示例:GPS定位数据(每条固定32字节)"); + System.out.println(); + System.out.println(" 2️⃣ 分隔符(DelimiterBasedFrameDecoder / LineBasedFrameDecoder)"); + System.out.println(" 核心特点:用特殊字符(如\\n、\\r\\n)标记消息结束"); + System.out.println(" 优点:支持变长消息,人类可读,调试方便"); + System.out.println(" 缺点:消息内容不能包含分隔符(需转义),需扫描每个字节找分隔符"); + System.out.println(" 典型应用:HTTP/1.x、SMTP、FTP、Telnet 等文本协议"); + System.out.println(" 示例:HTTP请求头以\\r\\n\\r\\n结束"); + System.out.println(); + System.out.println(" 3️⃣ 长度字段(LengthFieldBasedFrameDecoder)← 工业界首选"); + System.out.println(" 核心特点:消息 = 长度头 + 消息体,先读长度再读body"); + System.out.println(" 优点:支持变长,内容可含任意字节(包括分隔符),性能最优"); + System.out.println(" 缺点:需预先知道长度字段的位置和大小,配置参数较多"); + System.out.println(" 典型应用:Dubbo、gRPC、Thrift、Kafka协议、Redis协议"); + System.out.println(" 示例:[4字节长度][N字节body],长度值=N"); + System.out.println(); + System.out.println(" 4️⃣ 自定义协议(ReplayingDecoder / ByteToMessageDecoder)"); + System.out.println(" 核心特点:完整的协议设计,包含魔数、版本、类型、长度等字段"); + System.out.println(" 优点:最灵活,可扩展性强,支持协议演进和兼容性"); + System.out.println(" 缺点:开发成本高,需自行处理编解码逻辑"); + System.out.println(" 典型应用:自研RPC框架、游戏服务器协议、金融交易系统"); + System.out.println(" 示例:[魔数4B][版本1B][类型1B][序列号4B][长度4B][body]"); + System.out.println(); + System.out.println(" 【选择建议】"); + System.out.println(); + System.out.println(" ✅ 选固定长度:消息长度固定且较短(如传感器数据)"); + System.out.println(" ✅ 选分隔符: 文本协议、人类可读、调试友好(如HTTP)"); + System.out.println(" ✅ 选长度字段:高性能二进制协议、通用场景(强烈推荐!)"); + System.out.println(" ✅ 选自定义: 需要协议版本控制、复杂业务逻辑(如RPC)"); + System.out.println(); + System.out.println(" 【性能对比(吞吐量)】"); + System.out.println(" 固定长度 > 长度字段 > 分隔符 > 自定义协议"); + System.out.println(" (但实际差距不大,长度字段方案已足够优秀)"); + System.out.println(); + System.out.println(" 【Netty 内置 Decoder 速查】"); + System.out.println(" FixedLengthFrameDecoder(1024) → 固定1024字节"); + System.out.println(" LineBasedFrameDecoder(8192) → 按行分割(\\n或\\r\\n)"); + System.out.println(" DelimiterBasedFrameDecoder(8192, delim) → 自定义分隔符"); + System.out.println(" LengthFieldBasedFrameDecoder(...) → 长度字段(6个参数)"); + System.out.println(" ReplayingDecoder / ByteToMessageDecoder → 自定义协议"); } } From 7f13c18b277a87ee012ef96664068329828463db Mon Sep 17 00:00:00 2001 From: IE619 <984680432@qq.com> Date: Thu, 4 Jun 2026 21:56:50 +0800 Subject: [PATCH 12/12] =?UTF-8?q?Part19-=E5=86=85=E5=AE=B9=E8=A1=A5?= =?UTF-8?q?=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Part19_NettyPractice.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part19_NettyPractice.java b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part19_NettyPractice.java index 4d69281..00fe468 100644 --- a/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part19_NettyPractice.java +++ b/src/main/java/org/example/java_base_test/io/nio/show_multi_agent/Part19_NettyPractice.java @@ -266,6 +266,61 @@ static void explain() { System.out.println(" // 第一个参数 quietPeriod=0:没有新任务时立即关闭"); System.out.println(" // 第二个参数 timeout=5:最多等 5 秒"); System.out.println(); + + // ════════════════════════════════════════════════════════════════ + // 第五节:核心知识点总结 + // ════════════════════════════════════════════════════════════════ + System.out.println("━━━ 5. 核心知识点总结(你需要掌握什么)━━━━━━━━━━━━━━━━━━━━━━"); + System.out.println(); + System.out.println(" 【第一优先级:必须理解】"); + System.out.println(); + System.out.println(" 1️⃣ Boss/Worker线程模型"); + System.out.println(" Boss:只负责accept新连接(类似餐厅前台,1个线程足够)"); + System.out.println(" Worker:负责IO读写+业务处理(类似服务员,CPU×2个线程)"); + System.out.println(" 为什么分离?提高并发性能,accept不阻塞IO处理"); + System.out.println(); + System.out.println(" 2️⃣ Pipeline和Handler链"); + System.out.println(" 数据像流水线一样依次经过各个Handler"); + System.out.println(" Inbound方向:拆包→解码→业务(从外到内)"); + System.out.println(" Outbound方向:业务→编码→发送(从内到外)"); + System.out.println(" 顺序很重要!拆包必须在解码之前"); + System.out.println(); + System.out.println(" 3️⃣ 粘包/拆包处理"); + System.out.println(" TCP是字节流,不保证消息边界"); + System.out.println(" 可能一次读到多条消息(粘包)或一条分多次(拆包)"); + System.out.println(" 必须定义消息边界:固定长度、分隔符、长度字段"); + System.out.println(" Netty内置Decoder:LineBased/Delimiter/LengthField"); + System.out.println(); + System.out.println(" 4️⃣ 内存管理(ByteBuf引用计数)"); + System.out.println(" SimpleChannelInboundHandler:自动release(推荐)"); + System.out.println(" ChannelInboundHandlerAdapter:手动release"); + System.out.println(" 忘记release会导致堆外内存泄漏→OutOfMemoryError"); + System.out.println(" 原则:谁最后用,谁release"); + System.out.println(); + System.out.println(" 【第二优先级:应该理解】"); + System.out.println(); + System.out.println(" 5️⃣ 异步编程(ChannelFuture)"); + System.out.println(" Netty所有IO操作都是异步的,立即返回Future"); + System.out.println(" ❌ 不要在EventLoop线程中调用sync()(会死锁)"); + System.out.println(" ✅ 使用addListener添加回调(推荐)"); + System.out.println(" 可用常量:ChannelFutureListener.CLOSE等"); + System.out.println(); + System.out.println(" 6️⃣ HTTP协议处理"); + System.out.println(" HttpRequestDecoder:字节→HttpRequest+HttpContent"); + System.out.println(" HttpObjectAggregator:聚合分片请求为FullHttpRequest"); + System.out.println(" HttpResponseEncoder:HttpResponse→字节"); + System.out.println(" Spring Boot底层就是这样工作的"); + System.out.println(); + System.out.println(" 7️⃣ 连接池"); + System.out.println(" TCP三次握手耗时~1ms,高并发下频繁建连接性能差"); + System.out.println(" 复用连接:借acquire() → 使用 → 还release()"); + System.out.println(" RPC框架(Dubbo/gRPC)都用连接池"); + System.out.println(" 借了必须还,否则连接泄漏"); + System.out.println(); + System.out.println(" 8️⃣ 优雅关闭"); + System.out.println(" 不要直接System.exit()!可能有未处理的请求"); + System.out.println(" shutdownGracefully(quietPeriod, timeout, unit)"); + System.out.println(" quietPeriod=0:无新任务立即关闭"); NIODemo.printSeparator(); } }