Netty

netty咱们首先定个基调:Netty是java网络架构,根红苗正的java;现在技术成熟了,公司通信的场景不二人选,虽说现在有ai但是为什么用,怎么用更好还是咱们的基本功!

NIO与Netty

  • NIO 就像给你一本《如何造火箭》的书;Netty 是直接送你一艘 SpaceX,附赠马斯克当司机
  • javaNIO提供了非阻塞IO的底层能力(selector/channel/buffer这都是老朋友啦)但是api又臭又长,写起来挺麻烦:用螺丝刀组装战斗机的感脚

Netty = 异步事件驱动 + 高性能网络框架 + Java 程序员的 IO 救世主

⚡ 第二幕:Netty------"一场精心策划的 IO 政变"

我们可以把netty的出场想象成一场宫廷政变:几位朝廷老臣(Netty 核心开发者)早已看透:当今皇帝 BIO(Blocking IO) 暴虐无道------来一个使节(请求),就强征一名民夫(线程)全程伺候;万国来朝之日,便是国库破产、民怨沸腾之时!于是,他们秘密拥立 三皇子 Reactor ,誓要发动一场零流血、高效率、可持续 的政变:以事件为令,以异步为刃,建立新王朝!

  • 传统 BIO(Blocking IO)
    → 来一个包裹(请求),就雇一个快递员(线程)专门等它打包、发货。
    → 10000 个包裹?雇 10000 个快递员!
    → 结果:工资发破产,办公室挤成沙丁鱼罐头(线程上下文切换爆炸)。
  • Netty 的 Reactor 模型
    → 只雇 2 个小队
    • Boss 小队(1人) :专职守宫门,只接见新使节(accept),绝不参与后续
    • Worker 小队(CPU *2) :专职处理政务(read/write),绝不接触宫门 → 每个 Worker 是单线程 + 事件循环 ,像闪电侠在多个包裹间瞬移,绝不等待、绝不卡顿

🏰 政变步骤(对应代码)

复制代码
public class NettyServer {
    public static void main(String[] args) {
        #bossGroup只负责accept()新连接(像门卫)
        EventLoopGroup boss = new NioEventLoopGroup(1);// 1个足以,专管"接头"
        #Worker:负责所有 read/write(像特工小队)
        EventLoopGroup worker = new NioEventLoopGroup();// 默认 CPU*2 个Worker,干脏活读写

        try {
            new ServerBootstrap()
                .group(boss, worker) //指定死士队伍
                .channel(NioServerSocketChannel.class)//使用 NIO 通道(武器类型)
                .option(ChannelOption.SO_BACKLOG, 1024)//等待队列长度(政变预备队)配置 ServerSocketChannel(Boss 用)
                .childOption(ChannelOption.TCP_NODELAY, true)//禁用Nagle,消息立刻发!配置SocketChannel(Worker用)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {//注册你的业务 Handler
                        ch.pipeline().addLast(new EchoHandler());
                    }
                })
                .bind(8080).sync(); // ⚡ 关键!启动政变!(业务逻辑)

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

🔥 第一步:bind() → 点燃导火索!

重中之重的是那个最后的bind,如果没有bind那么一切都只是空想,不落地、空中楼阁,所以咱们现在看看具体是怎么个事,怎么就完美落地,政变成功了?

  • bind() 返回一个 ChannelFuture,内部调用``doBind(localAddress),然后我们来到了 initAndRegister():创建并注册了channel,看到没 环环相扣!这么重要的事情必定是设计地很缜密

    // AbstractBootstrap.java
    final ChannelFuture initAndRegister() {
    // 1.建宫殿,否则无法见使节------没有外应
    //创建 NioServerSocketChannel封装了 JDK 的 ServerSocketChannel
    Channel channel = channelFactory.newChannel();
    //派特工,使节天高皇帝远、无法策反
    init(channel);// 2. 初始化(加 Pipeline)
    // 通禁军咱们老巢在这,打起来 老巢要守住!
    //3. 注册到 Boss EventLoop
    ChannelFuture regFuture = config().group().register(channel);
    return regFuture;
    }

🏗️ 创建 NioServerSocketChannel ------ "打造影子宫殿"

上面channelFactory.newChannel()会调用:

复制代码
// NioServerSocketChannel 构造函数
public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER)); // 创建 JDK 的 ServerSocketChannel
}

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    return provider.openServerSocketChannel(); // 真正的底层 socket
}

这座宫殿有三大特征:

  1. 外表合法 :使用官方 ServerSocketChannel,骗过皇帝审查
  2. 内藏机关:自带 Pipeline(未来安插特工的通道)
  3. 预留兵营SO_BACKLOG=1024(政变预备队等待区)
    💡 此时宫殿已成,但大门紧闭、无人知晓------还需两步激活
📜 init(channel) ------ "安插征兵官(唯一特工)"
复制代码
@Override
void init(Channel channel) {
    // 把 childHandler(你的业务 Handler)存起来
    channel.attr(CHILD_HANDLER).set(childHandler);

    //给司令部 Pipeline 加一个"特工":"凡有新使节踏入此门立即带往Worker营地,为其洗脑(初始化业务Handler)
    //有新连接进来就把它分配给Worker小队并初始化它的 Pipeline!
    //Head → 征兵官(ServerBootstrapAcceptor) → Tail
    channel.pipeline().addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) {
            ch.pipeline().addLast(new ServerBootstrapAcceptor(
                currentChildGroup, currentChildHandler, ...));
        }
    });
}
🪖 register() ------ "打通禁军(Selector)"
复制代码
// MultithreadEventLoopGroup.register()
public ChannelFuture register(Channel channel) {
    return next().register(channel); // next() = Boss EventLoop(单线程)
}

//SingleThreadEventLoop.register():
@Override
public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}

@Override
protected void doRegister() {
    // ⚡ 核心!把 JDK 的 ServerSocketChannel 注册到 Selector 上
    selectionKey = javaChannel().register(selector, 0, channel);
}

至此

  • ServerSocketChannel 已注册到 Boss 的 Selector
  • 监听的是 OP_ACCEPT 事件(但初始 interestOps=0,稍后才设)

老臣重金收买禁军统领(Selector),将其耳目对准影子宫殿。

但注意 :此时只是"备案",尚未下令监视(interestOps=0)。

为何不直接开启监听?因为宫殿尚未挂牌(未 bind),若提前监听,禁军会报"地址无效"!

💡 这一步是"静默渗透"------让系统认可宫殿的存在,为最后一步铺路

🚩 :doBind() ------ "正式挂帅出征"
复制代码
// AbstractChannel.doBind()
@Override
protected void doBind(SocketAddress localAddress) {
    // 向皇帝申请:此地可接使节!
    javaChannel().socket().bind(localAddress, config.getBacklog()); // JDK bind
    pipeline.fireChannelActive(); // 触发 active 事件
}

//NioMessageUnsafe
@Override
protected void doBeginRead() {
    // 密令禁军:从此刻起,紧盯此门!
    selectionKey.interestOps(selectionKey.interestOps() | readInterestOp);
    // readInterestOp = OP_ACCEPT(对 ServerSocketChannel)
}

💥 BOOM!政变机器全面激活

皇帝批准(bind 成功)→ 影子宫殿合法化

禁军开始监视(OP_ACCEPT)→ 任何叩门声都将触发警报,✅ 至此,政变部署完成,只等第一个使节到来

🤝 :接受连接 ------ "征兵官登场"

当 Boss 线程在 select() 中收到 OP_ACCEPT:

复制代码
// NioMessageUnsafe.read()
for (;;) {
    SocketChannel ch = serverSocketChannel.accept(); // 接受新连接
    if (ch != null) {
        readPending = false;
        // ⚡ 关键!调用 Pipeline 的 fireChannelRead
        pipeline.fireChannelRead(ch);
    }
}

事件传递到 ServerBootstrapAcceptor

复制代码
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final SocketChannel child = (SocketChannel) msg;
    
    // 1. 把新连接注册到 Worker EventLoop
    childGroup.register(child).addListener(...);
    
    // 2. 初始化它的 Pipeline(加入你的 EchoHandler!)
    initChannel(child);
}

当使节叩门(客户端 connect):

  1. 禁军飞报 Boss:"有使节至!"(OP_ACCEPT 事件)
  2. Boss 开门迎接accept()),拿到使节名帖(新 SocketChannel)
  3. 立即触发 Pipeline征兵官现身
  4. 征兵官低语 :"随我来",并将使节:
    • 注册到 Worker 营地workerGroup.register()
    • 洗脑植入业务逻辑initChannel() → 加入 EchoHandler
  5. Worker 闪电接手,开始处理政务(read/write)

💥 整个过程无锁、无等待、无上下文切换 ------
Boss 从未踏入 Worker 营地,Worker 也从未见过宫门
全靠征兵官一人穿针引线

🎯 全流程图

复制代码
main()
  ↓
new ServerBootstrap()
  ↓
bind(8080)
  ├─ 创建 NioServerSocketChannel(司令部)
  ├─ 初始化 Pipeline(加入征兵官)
  ├─ 注册到 Boss EventLoop(Selector 监听 OP_ACCEPT)
  └─ bind() 系统调用 + 开启监听
        ↓
客户端 connect
        ↓
Boss 线程 accept() → fireChannelRead(new SocketChannel)
        ↓
ServerBootstrapAcceptor:
   ├─ 注册到 Worker EventLoop
   └─ 初始化子 Channel Pipeline(加入你的 Handler)
        ↓
Worker 线程处理 read/write

💡 记住
Boss 只管"招人"(accept),Worker 才是"干活的"(read/write) 至此就达到了旧IO皇帝无法达到的境地,历来被推倒的王朝都是可以推倒的;

💡再次强调: 为什么是"串行无锁"?

每个 Channel 只属于一个 Worker 线程 ,所有操作都在该线程的 EventLoop 中执行------天然线程安全,无需加锁!

🏁 善后(优雅关闭)漏掉了
复制代码
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();

✅ 会等所有任务处理完再 shutdown,避免请求丢失

💡 关键设计

  • 串行无锁化:每个 Channel 只被一个 EventLoop 处理,避免锁竞争
  • 减少上下文切换:线程数 ≈ CPU 核心数,效率拉满

好了,自己做主之后就开始吃香的喝辣的吧,大家都是"上we者"了,不要装 🐶

🧩 第三幕:Pipeline ------ "责任链的米其林厨房"

Netty 处理请求,就像一家米其林后厨

  • ChannelPipeline = 传送带
  • ChannelHandler = 厨师(每个只干一件事)
  • 请求数据 = 一份生牛排

流程:

复制代码
生牛排 → [解冻师傅] → [腌制大师] → [煎烤大厨] → [摆盘艺术家] → 上菜!

public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) {
        ch.pipeline()
          .addLast(new HttpRequestDecoder())     // HTTP 解码器(解冻)
          .addLast(new HttpObjectAggregator(65536)) // 合并分块(腌制)
          .addLast(new HttpResponseEncoder())    // HTTP 编码器(煎烤)
          .addLast(new MyBusinessHandler());     // 你的业务逻辑(摆盘)
    }
}

优势

  • 关注点分离:解码、业务、编码各司其职
  • 灵活组装 :想加"辣椒粉"(压缩)?插个 HttpContentCompressor 就行!
  • 复用性强:这套流水线,TCP/HTTP/WebSocket 都能跑!

🧠 第四幕:异步与 Future ------ "先开单,后上菜"

  • netty的世界里你不需要等待,想写数据到网络:直接返回future(像一张"取餐号")

    ChannelFuture future = ctx.writeAndFlush(response);

  • 你可以说:"菜好了叫我!"(加监听器)

    future.addListener(f -> {
    if (f.isSuccess()) {
    System.out.println("客户收到菜了!");
    } else {
    System.out.println("厨房着火了!" + f.cause());
    }
    });

💡 哲学
"别等我,你先忙,好了喊你" ------ 这就是异步非阻塞的精髓!

🧵 第五幕:内存管理 ------ "堆外内存的精算师"

Netty 为了快,直接绕过 JVM 堆内存 ,用 Direct Buffer(堆外内存)

  • 传统方式:数据 → JVM 堆 → 复制到内核 → 网卡(2次拷贝)
  • Netty 方式:数据 → 堆外内存 → 内核 → 网卡(1次拷贝)

但堆外内存创建/销毁很贵?Netty 早想到了!

🏦 内存池(PooledByteBufAllocator)

  • 向 OS 一次性申请大块内存(Chunk = 16MB)
  • 切成小块(Page = 8KB)再细分(Subpage)
  • 线程用完不释放,放回"本地缓存",下次直接复用!

效果

零拷贝 + 内存池 = 高频网络传输不 GC、不卡顿

🛡️ 第六幕:健壮性 ------ "自带防弹衣的战士"

Netty 不只是快,还稳如老狗

问题 Netty 的解决方案
TCP 粘包/拆包 LengthFieldBasedFrameDecoder 自动切包
心跳检测 IdleStateHandler + 自定义心跳包
Epoll 空轮询 Bug 检测到空转超阈值,重建 Selector
内存泄漏 ReferenceCountUtil.release() + ResourceLeakDetector

🧩TCP 粘包/拆包:自定义协议编解码实战 ------ "造一套黑话系统"

💡 粘包问题:

客户连续发 "Hello" "World",TCP 可能合成 "HelloWorld" 或拆成 "Hel" "loWorld"

而我们即深谋远虑、顾全大局又锱铢必较、看重小节的Netty,他手下的兵Decoder 会按长度字段自动重组,业务层永远拿到完整消息!

在现实中TCP 是字节流 ,没有消息边界,放到汪洋大海中,谁知道

你发 "login:alice" + "msg:hello",对方可能收到:

  • "login:alicemsg:hello" (粘包)
  • "log" + "in:alice..." (拆包)

能做到如此,是因为手握两大法宝:定义协议 + 编解码器

📜 协议设计

我们设计一个长度 + 内容的私有协议:

4字节长度\]\[JSON内容\]0000001A{"cmd":"login","user":"alice"} ###### 🔧 步骤 1:写 Decoder(拆包 + 解析) //ByteToMessageDecoder 自动处理粘包/拆包 public class MyMessageDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { // 如果可读字节 < 4(长度字段),直接返回(等更多数据) if (in.readableBytes() < 4) return; // 标记 readerIndex(万一不够,可以 reset 回来) in.markReaderIndex(); // 读取4字节长度 int length = in.readInt(); // 如果剩余字节 < length,说明包不完整 if (in.readableBytes() < length) { in.resetReaderIndex(); // 回退,等下次 return; } // 读取完整消息 byte[] content = new byte[length]; in.readBytes(content); String json = new String(content, StandardCharsets.UTF_8); MyMessage msg = JSON.parseObject(json, MyMessage.class); out.add(msg); // 交给下一个 Handler } } 代码有多次等待,等待拿到完整消息,不管tcp怎么切,值得的人值得多次等待~ ###### 🔧 第二步:写 Encoder(打包) public class MyProtocolEncoder extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) { byte[] data = msg.getBytes(StandardCharsets.UTF_8); out.writeInt(data.length); // 写4字节长度 out.writeBytes(data); // 写body } } ###### 🔌 第三步:组装 Pipeline public class MyChannelInitializer extends ChannelInitializer { @Override protected void initChannel(SocketChannel ch) { ch.pipeline() .addLast(new MyProtocolDecoder()) // 先解码 .addLast(new MyProtocolEncoder()) // 再编码 .addLast(new MyBusinessHandler()); // 你的业务 } } ###### 💼 第四步:业务 Handler(收发字符串!) public class MyBusinessHandler extends SimpleChannelInboundHandler { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { System.out.println("收到: " + msg); ctx.writeAndFlush("Echo: " + msg); //发送:直接回字符串! } } 是否注意到,channelRead0收到的是string字符串,发送时也是字符串msg ###### 测试: // 客户端 Pipeline 也加同样的 Encoder/Decoder ChannelFuture f = bootstrap.connect("127.0.0.1", 8080).sync(); f.channel().writeAndFlush("Hello Netty!"); ### 总结 "Netty 是一个基于 NIO 的异步事件驱动网络框架,采用主从 **Reactor** 线程模型,通过 EventLoop 实现**串行无锁化**处理。其核心 Pipeline 机制基于责任链模式,支持高度可插拔的编解码与业务处理。同时,Netty 通过**堆外内存池、零拷贝、内存引用计数** 等技术,在保证高吞吐低延迟的同时,避免了内存泄漏与 GC 压力。最后,内置的**粘包拆包处理器、心跳机制和 Epoll 空轮询**修复,使其在生产环境具备极强的健壮性。" | 超能力 | 技术实现 | 效果 | |--------|-------------------------------|---------------| | 闪电速度 | Reactor 模型 + epoll | 单机轻松扛 10W+ 并发 | | 模块化流水线 | ChannelPipeline + Handler | 业务解耦,灵活扩展 | | 异步不阻塞 | ChannelFuture + Listener | 资源利用率 100% | | 内存精算师 | PooledByteBuf + Direct Memory | 零拷贝 + 无 GC 停顿 | | 钢铁之躯 | 粘包处理 + 心跳 + Bug 修复 | 7×24 小时稳定运行 |

相关推荐
●VON2 小时前
Flutter for OpenHarmony:基于 SharedPreferences 的本地化笔记应用架构与实现
笔记·学习·flutter·ui·架构·openharmony·von
九皇叔叔2 小时前
【06】SpringBoot3 MybatisPlus 修改(Mapper)
java·spring boot·mybatis·mybatisplus
如果'\'真能转义说2 小时前
Spring 概述
java·spring
cyforkk2 小时前
07、Java 基础硬核复习:面向对象编程(进阶)的核心逻辑与面试考点
java·开发语言·面试
曾卫2 小时前
java.lang.*中Class 源代码详解【五】
java·源码
zhougl9962 小时前
Java定时任务实现
java·开发语言·python
2601_949575862 小时前
Flutter for OpenHarmony艺考真题题库+个人信息管理实现
java·前端·flutter
zhougl9962 小时前
继承成员变量和继承方法的区别
java·开发语言
heartbeat..2 小时前
Redis Cluster (Redis 集群模式)从入门到精通:架构解析、机制详解与运维排查
java·运维·redis·架构·nosql