【netty】基于主从Reactor多线程模型|如何解决粘包拆包问题|零拷贝

1、基于Selector的NIO

基础概念:

ServerSocketChannel:服务器监听通道

  • 监听指定端口,接受连接请求
  • 每个服务器通常只有一个
  • 只关注OP_ACCEPT事件

SocketChannel:客户端通信通道

  • 与特定客户端进行数据交换
  • 服务器有多个(每个客户一个)
  • 关注OP_READ和OP_WRITE事件

原生基于Selector的NIO代码:

java 复制代码
public class CompleteNioServer {
    private Selector selector;
    private ServerSocketChannel serverChannel;
    private final int PORT = 8080;
    
    public static void main(String[] args) throws IOException {
        new CompleteNioServer().start();
    }
    
    public void start() throws IOException {
        // 初始化
        selector = Selector.open();
        serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.socket().bind(new InetSocketAddress(PORT));
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        System.out.println("NIO服务器启动,端口: " + PORT);
        
        // 事件循环
        eventLoop();
    }
    
    private void eventLoop() {
        while (true) {
            try {
                // 等待事件
                selector.select();
                
                // 处理所有就绪事件
                Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                while (keys.hasNext()) {
                    SelectionKey key = keys.next();
                    keys.remove();
                    
                    if (!key.isValid()) continue;
                    
                    if (key.isAcceptable()) {
                        acceptClient(key);
                    } else if (key.isReadable()) {
                        readData(key);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    private void acceptClient(SelectionKey key) throws IOException {
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        SocketChannel client = server.accept();
        client.configureBlocking(false);
        client.register(selector, SelectionKey.OP_READ);
        System.out.println("客户端连接: " + client.getRemoteAddress());
    }
    
    private void readData(SelectionKey key) throws IOException {
        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        int bytesRead = client.read(buffer);
        if (bytesRead == -1) {
            client.close();
            key.cancel();
            System.out.println("客户端断开");
            return;
        }
        
        if (bytesRead > 0) {
            buffer.flip();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            String message = new String(data).trim();
            System.out.println("收到: " + message);
            
            // 回显
            String response = "Echo: " + message + "\n";
            client.write(ByteBuffer.wrap(response.getBytes()));
        }
    }
}

由以上代码可以看到:

Selector同时注册在ServerSocketChannel和多个SocketChannel上,Selector同时监听连接、读、写事件。

具体selector监控原理:(高性能的体现)

java 复制代码
// 应用程序调用select(),线程阻塞,不消耗CPU资源
selector.select();

// 底层:
// - 应用程序线程进入休眠状态
// - 操作系统内核监控所有注册的fd
// - 当有事件时,内核唤醒应用程序线程

2、Reactor模型

以上基于Selector的原生NIO代码可以看出,使用了selector这个工具,但需要自己设计架构。

java 复制代码
while (true) {
    selector.select();
    Set<SelectionKey> keys = selector.selectedKeys();
    
    for (SelectionKey key : keys) {
        if (key.isAcceptable()) {
            // 需要自己处理连接建立
        } else if (key.isReadable()) {
            // 需要自己处理数据读取
        } else if (key.isWritable()) {
            // 需要自己处理数据写入
        }
    }
    // 问题:所有事情都在一个线程中处理,性能瓶颈!
}

Reactor模型解决了"如何使用Selector"的问题:

它的思想就是:

"不要把所有事情都塞给一个Selector处理

要分工协作,各司其职"

阶段1:单线程Reactor(直接使用Selector)
java 复制代码
// 一个人干所有事情
单线程:接受连接 + 读取数据 + 业务处理 + 发送响应
    ↓
问题:业务处理阻塞了新连接接受
阶段2:多线程Reactor(Selector + 线程池)
java 复制代码
// 主线程:接受连接 + IO读写
// 线程池:业务处理
主线程(Selector) → 检测IO事件 → 提交业务到线程池
    ↓
问题:IO读写和业务处理分离,但连接建立仍在主线程
阶段3:主从Reactor(多Selector分工)
java 复制代码
// Boss Reactor:专门接受连接
// Worker Reactor:专门处理IO
// 线程池:专门业务处理

Boss Selector(连接建立) → Worker Selector(IO读写) → 线程池(业务处理)

3、Netty基于主从Reactor多线程模型

与原生NIO相比,Netty做了什么优化?

  1. 线程模型优化:原生NIO需要自己设计,Netty是基于主从Reactor模型的完整实现,不用手动基于Selector去实现所有细节,Netty提供了开箱即用的最佳实践。

  2. 内存管理优化:ByteBuf池化、零拷贝,减少GC压力

  3. 协议支持完善:内置各种编解码器,解决粘包拆包问题

  4. 异常处理健全:连接异常、超时等都有完善的处理机制

以以上优化点为思路,我们来依次学习netty相关的技术点:

1、基于主从Reactor多线程模型

Netty的主从Reactor模型与原生的NIO有本质区别,主要体现在Selector架构上:

原生NIO通常使用单Selector模型:

  • 一个Selector监控所有Channel(ServerSocketChannel + 所有SocketChannel)
  • 所有事件类型(ACCEPT、READ、WRITE)都由同一个Selector处理
  • 单个线程成为性能瓶颈,无法充分利用多核CPU

Netty采用多Selector模型:

  • Boss Group使用专用的Selector,只监听ServerSocketChannel的ACCEPT事件
  • Worker Group使用多个Selector,每个Worker线程有自己的Selector
  • 每个Worker Selector只管理一部分SocketChannel的READ/WRITE事件
  • 真正实现了多线程并行处理IO事件
text 复制代码
时间点0: 服务器启动
    ↓
Boss Group创建 → Boss EventLoop创建 → Boss Selector创建
    ↓
ServerSocketChannel创建 → 注册到Boss Selector (监听OP_ACCEPT)
    ↓
时间点1: 客户端连接
    ↓  
Boss Selector检测到OP_ACCEPT事件
    ↓
创建SocketChannel → 分配给Worker Group
    ↓
Worker EventLoop将SocketChannel注册到自己的Selector (监听OP_READ)
    ↓
时间点2: 客户端发送数据
    ↓
Worker Selector检测到OP_READ事件 → 处理数据

总结:

在Netty中:

  • 每个EventLoop(线程)都有自己的Selector
  • Channel一旦注册到某个EventLoop,就终身绑定
  • 所有对该Channel的操作都在同一个线程中执行

这样设计确保了:

  • 无锁化:没有线程竞争
  • 顺序性:消息按发送顺序处理
2、如何解决粘包拆包问题

待补......

3、零拷贝

待补......

相关推荐
鸠摩智首席音效师5 小时前
linux 系统中 Shutting Down, Restarting, Halting 有什么区别 ?
linux·运维·服务器
CIb0la5 小时前
Linux 将继续不支持 HDMI 2.1 实现
linux·运维·服务器
CoderYanger5 小时前
C.滑动窗口-求子数组个数-越长越合法——2799. 统计完全子数组的数目
java·c语言·开发语言·数据结构·算法·leetcode·职场和发展
C++业余爱好者5 小时前
Java 提供了8种基本数据类型及封装类型介绍
java·开发语言·python
想用offer打牌5 小时前
RocketMQ如何防止消息丢失?
java·后端·架构·开源·rocketmq
老蒋新思维5 小时前
创客匠人峰会深度解析:知识变现的 “信任 - 效率” 双闭环 —— 从 “单次交易” 到 “终身复购” 的增长密码
大数据·网络·人工智能·tcp/ip·重构·数据挖掘·创客匠人
皮卡龙5 小时前
Java常用的JSON
java·开发语言·spring boot·json
卓码软件测评5 小时前
第三方高校软件课题验收测试机构:【使用Apifox测试gRPC服务步骤和技巧】
网络·测试工具·测试用例
掘根6 小时前
【消息队列】交换机数据管理实现
网络·数据库
利刃大大6 小时前
【JavaSE】十三、枚举类Enum && Lambda表达式 && 列表排序常见写法
java·开发语言·枚举·lambda·排序