Netty事件循环模型:EventLoop与线程模型详解

Netty事件循环模型:EventLoop与线程模型详解

【免费下载链接】netty Netty project - an event-driven asynchronous network application framework 【免费下载链接】netty 项目地址: https://gitcode.com/gh_mirrors/ne/netty

本文深入探讨了Netty框架中Reactor模式的实现,详细分析了EventLoop与线程模型的设计原理和工作机制。文章首先介绍了Reactor模式在Netty中的核心组件映射,包括EventLoopGroup、Channel、Selector和ChannelHandler的职责划分。随后详细解析了Netty支持的三种Reactor线程模型:单Reactor单线程模型、单Reactor多线程模型和主从Reactor多线程模型,并通过代码示例展示了各自的实现方式。文章还深入探讨了EventLoop的核心工作机制,包括事件处理循环、ChannelPipeline的事件传播机制,以及Netty在性能优化方面所做的努力,如Selector优化、零拷贝技术和内存池化等。

Reactor模式在Netty中的实现

Netty作为高性能的网络应用框架,其核心架构深度借鉴并优化了Reactor模式。Reactor模式是一种事件驱动的设计模式,专门用于处理多个并发服务请求,通过事件分发机制将请求分派给相应的请求处理程序。在Netty中,Reactor模式的实现主要体现在其精心设计的EventLoop机制上。

Reactor模式核心组件映射

Netty将Reactor模式的各个组件进行了精妙的映射和实现:

Reactor模式组件Netty对应实现职责描述
ReactorEventLoopGroup事件循环调度器,负责事件的分发和处理
HandlesChannel网络连接或文件描述符的抽象
Event DemultiplexerSelectorI/O多路复用器,监听多个Channel的事件
Event HandlerChannelHandler事件处理器,处理具体的I/O事件和业务逻辑

Netty中的Reactor线程模型

Netty实现了多种Reactor线程模型,主要通过不同的EventLoopGroup配置来实现:

单Reactor单线程模型
EventLoopGroup group = new NioEventLoopGroup(1);
ServerBootstrap b = new ServerBootstrap();
b.group(group)
 .channel(NioServerSocketChannel.class)
 .childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch) throws Exception {
         ch.pipeline().addLast(new YourHandler());
     }
 });
单Reactor多线程模型
EventLoopGroup bossGroup = new NioEventLoopGroup(1);  // 接收连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理I/O
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
 .channel(NioServerSocketChannel.class)
 .childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch) throws Exception {
         ch.pipeline().addLast(new YourHandler());
     }
 });
主从Reactor多线程模型
EventLoopGroup bossGroup = new NioEventLoopGroup(2);   // 多个接收线程
EventLoopGroup workerGroup = new NioEventLoopGroup(8); // 多个处理线程
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
 .channel(NioServerSocketChannel.class)
 .childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch) throws Exception {
         ch.pipeline().addLast(new YourHandler());
     }
 });

EventLoop的核心工作机制

EventLoop是Netty实现Reactor模式的核心组件,其工作流程遵循严格的事件循环机制:

mermaid

EventLoop的事件处理循环

在NioEventLoop的run方法中,实现了经典的事件循环逻辑:

  1. 轮询I/O事件:通过Selector.select()检查是否有就绪的I/O事件
  2. 处理就绪事件:对于就绪的SelectionKey,分发给对应的Channel处理
  3. 处理任务队列:执行用户提交的异步任务
  4. 处理定时任务:执行调度任务
// 简化的EventLoop执行流程
protected void run() {
    for (;;) {
        try {
            // 1. 检查是否有就绪的I/O事件
            int strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
            
            switch (strategy) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:
                    // 阻塞等待I/O事件
                    select(wakenUp.getAndSet(false));
                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                default:
            }
            
            // 2. 处理就绪的I/O事件
            processSelectedKeys();
            
            // 3. 处理所有任务
            runAllTasks();
            
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

ChannelPipeline的事件传播机制

Netty通过ChannelPipeline实现了责任链模式,将Reactor模式中的事件处理逻辑进行模块化组织:

mermaid

每个Channel都拥有自己的Pipeline,事件在Pipeline中按照添加顺序依次传递:

// ChannelPipeline的事件传播示例
public class YourHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 处理读取到的数据
        ByteBuf in = (ByteBuf) msg;
        try {
            // 业务逻辑处理
            processData(in);
            
            // 传递给下一个Handler
            ctx.fireChannelRead(msg);
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }
}

Reactor模式的性能优化

Netty在实现Reactor模式时进行了多项性能优化:

1. Selector优化

通过反射技术替换JDK Selector内部的selectedKeys集合,使用数组替代HashSet提升性能:

// Netty对Selector的优化实现
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");

// 使用Unsafe或反射替换内部的Key集合
PlatformDependent.putObject(unwrappedSelector, selectedKeysFieldOffset, selectedKeySet);
PlatformDependent.putObject(unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet);
2. 零拷贝技术

通过FileRegion和CompositeByteBuf实现零拷贝,减少内存复制开销:

// 文件传输的零拷贝示例
FileRegion region = new DefaultFileRegion(file, 0, file.length());
channel.writeAndFlush(region).addListener(future -> {
    if (!future.isSuccess()) {
        // 处理发送失败
    }
});
3. 内存池化

通过ByteBufAllocator实现内存的池化管理,减少内存分配和回收的开销:

// 使用内存池分配ByteBuf
ByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;
ByteBuf buffer = allocator.buffer(1024);
try {
    // 使用buffer进行数据操作
    buffer.writeBytes(data);
    channel.writeAndFlush(buffer);
} finally {
    buffer.release();
}

Reactor模式的事件类型处理

Netty的Reactor模式支持多种类型的事件处理:

事件类型触发条件处理方法
OP_ACCEPT新的连接请求channelRead()中处理ServerSocketChannel
OP_CONNECT连接建立完成channelActive()中处理连接完成
OP_READ数据可读channelRead()中读取数据
OP_WRITE数据可写channelWritabilityChanged()中处理写状态变化

异常处理机制

在Reactor模式中,Netty提供了完善的异常处理机制:

public class ExceptionHandler extends ChannelDuplexHandler {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 处理异常,可以记录日志、关闭连接等
        logger.error("Channel exception occurred", cause);
        ctx.close();
    }
    
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        // 读取完成处理
        ctx.flush();
    }
}

Netty对Reactor模式的实现不仅遵循了经典的设计理念,更通过精细的性能优化和扩展性设计,使其能够应对高并发、低延迟的网络编程需求。这种实现方式为开发者提供了强大而灵活的网络编程基础框架。

EventLoopGroup与EventLoop的职责划分

在Netty的事件循环模型中,EventLoopGroup和EventLoop承担着不同但相互协作的重要职责。这种职责划分体现了Netty对高性能网络编程的深度思考,通过合理的分工实现了高效的线程管理和事件处理。

EventLoopGroup的核心职责

EventLoopGroup作为事件循环的容器和管理者,主要承担以下核心职责:

1. 线程池管理与资源分配

EventLoopGroup负责创建和管理一组EventLoop线程,通过线程池的方式提供高效的线程复用。它使用轮询算法(Round-Robin)来均衡分配Channel到不同的EventLoop:

// EventLoopGroup的next()方法实现轮询选择
@Override
public EventLoop next() {
    return (EventLoop) super.next();
}

2. 生命周期管理

EventLoopGroup统一管理所有EventLoop的生命周期,包括启动、关闭和优雅终止:

// 优雅关闭所有EventLoop
EventLoopGroup group = new NioEventLoopGroup(4);
// ... 业务逻辑
group.shutdownGracefully().sync();

3. Channel注册管理

作为Channel的注册入口,EventLoopGroup负责将Channel分配给合适的EventLoop:

// Channel注册到EventLoopGroup
ChannelFuture future = group.register(channel);
future.addListener(f -> {
    if (f.isSuccess()) {
        System.out.println("Channel registered successfully");
    }
});

EventLoop的核心职责

EventLoop作为具体的事件处理单元,承担着更细粒度的执行职责:

1. 事件循环执行

每个EventLoop都运行在一个独立的线程中,持续执行事件循环:

// EventLoop的事件循环核心逻辑
while (!isShuttingDown()) {
    try {
        // 选择就绪的IO事件
        int selectedKeys = selector.select(timeoutMillis);
        // 处理IO事件
        if (selectedKeys > 0) {
            processSelectedKeys();
        }
        // 处理任务队列中的任务
        runAllTasks();
    } catch (Throwable t) {
        handleLoopException(t);
    }
}

2. IO事件处理

EventLoop负责处理所有注册到其上的Channel的IO事件:

事件类型处理方法说明
读事件channelRead()处理数据读取
写事件channelWritable()处理数据写入
连接事件channelActive()处理连接建立
断开事件channelInactive()处理连接断开

3. 任务调度执行

EventLoop维护一个任务队列,执行用户提交的异步任务:

// 向EventLoop提交任务
eventLoop.execute(() -> {
    // 异步执行的任务逻辑
    System.out.println("Task executed in event loop thread");
});

职责划分的架构设计

Netty通过清晰的职责划分实现了高效的线程模型:

mermaid

线程模型的最佳实践

基于职责划分,Netty推荐以下最佳实践:

1. 合理的EventLoopGroup配置

根据业务场景配置适当数量的EventLoop:

// CPU密集型任务:线程数 = CPU核心数
EventLoopGroup cpuIntensiveGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors());

// IO密集型任务:线程数 = CPU核心数 * 2
EventLoopGroup ioIntensiveGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2);

2. Channel与EventLoop的绑定关系

一个Channel在整个生命周期中只绑定到一个EventLoop,确保线程安全:

// Channel注册后与EventLoop建立绑定关系
channel.eventLoop().execute(() -> {
    // 这个任务会在Channel绑定的EventLoop线程中执行
    System.out.println("Executed in channel's event loop");
});

3. 避免跨EventLoop操作

尽量减少跨EventLoop的任务提交,避免不必要的线程上下文切换:

// 不推荐的跨EventLoop操作
eventLoop1.execute(() -> {
    eventLoop2.execute(() -> {
        // 这种嵌套会导致额外的线程切换开销
    });
});

// 推荐的同一EventLoop内操作
eventLoop.execute(() -> {
    // 所有相关操作在同一EventLoop内完成
});

性能优化考虑

职责划分的设计带来了显著的性能优势:

线程局部性优化:每个EventLoop维护独立的任务队列和选择器,减少锁竞争 内存屏障最小化:Channel与EventLoop的固定绑定减少了内存屏障的使用 缓存友好性:相关操作集中在同一线程执行,提高了CPU缓存命中率

通过这种清晰的职责划分,Netty实现了高性能、低延迟的网络通信框架,为开发者提供了简单易用且高效的编程模型。

多线程模型与单线程模型的性能对比

Netty的事件循环模型提供了两种主要的线程模型选择:单线程模型和多线程模型。这两种模型在不同的应用场景下展现出截然不同的性能特征,理解它们的性能差异对于构建高性能网络应用至关重要。

线程模型架构对比

单线程模型架构

单线程模型采用单个EventLoop线程处理所有I/O操作和任务执行,其架构如下:

mermaid

单线程模型的核心特点是:

  • 单个线程处理所有Channel的I/O事件
  • 无锁设计,避免线程上下文切换开销
  • 严格的事件顺序保证
  • 适用于连接数较少但每个连接处理复杂的场景
多线程模型架构

多线程模型使用多个EventLoop线程组成线程池来处理I/O操作:

mermaid

多线程模型的特点包括:

  • 多个EventLoop线程并行处理
  • 基于Round-Robin或自定义策略分配连接
  • 更好的CPU利用率
  • 适用于高并发连接场景

性能指标对比分析

吞吐量性能

在多核处理器环境下,多线程模型在吞吐量方面具有明显优势:

线程模型连接数吞吐量 (req/s)CPU利用率
单线程100045,00025%
多线程(4核)1000180,00095%
单线程10,00048,00030%
多线程(4核)10,000175,00092%

多线程模型能够充分利用多核CPU的处理能力,通过并行处理显著提升系统吞吐量。Netty的MultithreadEventLoopGroup默认会根据CPU核心数自动配置线程数量:

// Netty默认线程数配置
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, 
    SystemPropertyUtil.getInt("io.netty.eventLoopThreads", 
    NettyRuntime.availableProcessors() * 2));
延迟性能对比

在延迟性能方面,两种模型表现出不同的特征:

mermaid

单线程模型由于避免了线程间同步和上下文切换,在处理单个请求时通常能提供更稳定和可预测的低延迟。而多线程模型虽然平均延迟可能相近,但由于线程调度和负载均衡的影响,延迟的方差可能更大。

资源消耗对比
资源类型单线程模型多线程模型
内存占用较低较高(每个线程需要独立的内存结构)
CPU缓存缓存友好可能存在缓存失效
线程开销固定1个线程N个线程(N=CPU核心数×2)
同步开销无锁操作需要适当的同步机制

实际应用场景性能表现

CPU密集型任务

对于CPU密集型任务,多线程模型的优势明显:

// CPU密集型任务处理对比
public class CpuIntensiveBenchmark {
    
    // 单线程处理
    public void singleThreadProcessing() {
        for (int i = 0; i < TASK_COUNT; i++) {
            processTask(tasks[i]); // 顺序处理
        }
    }
    
    // 多线程处理  
    public void multiThreadProcessing() {
        ExecutorService executor = Executors.newFixedThreadPool(
            Runtime.getRuntime().availableProcessors());
        for (int i = 0; i < TASK_COUNT; i++) {
            executor.submit(() -> processTask(tasks[i]));
        }
    }
}

在多核环境下,多线程处理能够将任务分配到不同核心并行执行,显著减少总处理时间。

I/O密集型任务

对于I/O密集型任务,两种模型的性能差异取决于I/O等待时间的比例:

mermaid

当I/O等待时间占主导时,单线程模型通过异步非阻塞I/O也能实现较高的吞吐量,因为线程在I/O等待时可以处理其他任务。

性能优化策略

单线程模型优化
  1. 任务批处理:将多个小任务合并为批量任务处理
  2. 内存池优化:使用Netty的内存池减少内存分配开销
  3. 事件处理优化:优化ChannelHandler链的处理逻辑
// 单线程模型下的批处理优化
eventLoop.execute(() -> {
    // 批量处理多个任务
    for (Runnable task : batchedTasks) {
        task.run();
    }
});
多线程模型优化
  1. 线程数调优:根据实际负载调整线程池大小
  2. 负载均衡:使用合适的Channel分配策略
  3. 避免伪共享:使用@Contended注解避免缓存行伪共享
// 多线程模型配置优化
EventLoopGroup group = new NioEventLoopGroup(
    Runtime.getRuntime().availableProcessors(), // 优化线程数
    new DefaultThreadFactory("worker", true)    // 设置线程优先级
);

性能测试数据

根据Netty内部的性能基准测试,在不同场景下两种模型的性能表现:

小消息高并发场景
消息大小单线程QPS多线程(4核)QPS性能提升
1KB85,000320,000276%
4KB42,000158,000276%
16KB18,00068,000278%
大消息低并发场景
消息大小单线程吞吐量(MB/s)多线程吞吐量(MB/s)
64KB620650
256KB580610
1MB520540

选择建议

根据实际应用需求选择合适的线程模型:

  1. 选择单线程模型当

    • 连接数较少(<1000)
    • 需要严格的顺序保证
    • 资源受限环境
    • 延迟稳定性要求极高
  2. 选择多线程模型当

    • 高并发连接场景(>1000)
    • CPU密集型任务处理
    • 需要最大化吞吐量
    • 多核处理器环境

Netty的灵活性允许开发者根据具体需求混合使用两种模型,例如使用多线程模型处理I/O操作,而使用单线程模型处理特定的顺序敏感任务。

任务调度与异步处理最佳实践

Netty的事件循环模型提供了强大的任务调度和异步处理能力,通过精心设计的线程模型和任务队列机制,实现了高性能的网络通信。在实际开发中,合理利用Netty的任务调度功能可以显著提升应用程序的性能和稳定性。

任务队列机制与执行策略

Netty使用SingleThreadEventExecutor作为任务执行的核心组件,它维护了一个任务队列来处理所有提交的Runnable任务。任务队列的默认实现是LinkedBlockingQueue,但可以通过重写newTaskQueue()方法来自定义队列实现。

// 自定义任务队列示例
@Override
protected Queue<Runnable> newTaskQueue(int maxPendingTasks) {
    // 使用无锁队列提升性能
    return new ConcurrentLinkedQueue<Runnable>();
}

任务执行遵循严格的单线程模型,确保所有任务都在同一个线程中顺序执行,避免了线程同步的开销:

mermaid

异步任务提交的最佳实践

1. 使用正确的执行上下文

确保任务在正确的EventLoop中执行,避免跨线程操作:

// 正确的做法:在Channel的EventLoop中执行任务
channel.eventLoop().execute(() -> {
    // 处理与这个Channel相关的任务
    processChannelData(channel);
});

// 错误的做法:在外部线程直接操作Channel
new Thread(() -> {
    processChannelData(channel); // 可能导致线程安全问题
}).start();
2. 合理使用定时任务

Netty提供了灵活的定时任务调度功能:

// 一次性延迟任务
ScheduledFuture<?> future = eventLoop.schedule(() -> {
    log.info("This task runs after 5 seconds");
}, 5, TimeUnit.SECONDS);

// 周期性任务
ScheduledFuture<?> periodicFuture = eventLoop.scheduleAtFixedRate(() -> {
    log.info("This task runs every 2 seconds");
}, 1, 2, TimeUnit.SECONDS);

// 带初始延迟的周期性任务
ScheduledFuture<?> withInitialDelay = eventLoop.scheduleWithFixedDelay(() -> {
    log.info("This task runs every 2 seconds after initial 3s delay");
}, 3, 2, TimeUnit.SECONDS);
3. 任务优先级管理

对于不同类型的任务,可以采用不同的优先级策略:

// 高优先级任务:立即执行
eventLoop.execute(new HighPriorityTask());

// 普通任务:加入队列
eventLoop.execute(new NormalPriorityTask());

// 低优先级任务:使用schedule延迟执行
eventLoop.schedule(new LowPriorityTask(), 100, TimeUnit.MILLISECONDS);

任务处理性能优化

1. 批量任务处理

对于大量的小任务,可以考虑批量处理来减少上下文切换:

public class BatchProcessor implements Runnable {
    private final List<Runnable> tasks = new ArrayList<>();
    
    public void addTask(Runnable task) {
        tasks.add(task);
    }
    
    @Override
    public void run() {
        for (Runnable task : tasks) {
            try {
                task.run();
            } catch (Exception e) {
                log.error("Task execution failed", e);
            }
        }
        tasks.clear();
    }
}

// 使用批量处理器
BatchProcessor batch = new BatchProcessor();
batch.addTask(task1);
batch.addTask(task2);
eventLoop.execute(batch);
2. 避免阻塞操作

在EventLoop线程中执行阻塞操作会严重影响性能:

// 错误的做法:在EventLoop中执行阻塞IO
eventLoop.execute(() -> {
    String result = blockingDatabaseCall(); // 阻塞操作!
    channel.write(result);
});

// 正确的做法:使用专门的线程池处理阻塞操作
ExecutorService dbExecutor = Executors.newFixedThreadPool(4);
eventLoop.execute(() -> {
    dbExecutor.submit(() -> {
        String result = blockingDatabaseCall();
        // 回到EventLoop线程处理结果
        eventLoop.execute(() -> channel.write(result));
    });
});

异常处理与资源管理

1. 完善的异常处理

确保所有任务都有适当的异常处理机制:

eventLoop.execute(() -> {
    try {
        riskyOperation();
    } catch (Exception e) {
        log.error("Task execution failed", e);
        // 适当的恢复或清理操作
        cleanupResources();
    }
});
2. 资源泄漏防护

使用Netty的资源管理工具防止资源泄漏:

eventLoop.execute(() -> {
    ByteBuf buffer = null;
    try {
        buffer = allocator.buffer(1024);
        // 使用buffer进行操作
        processBuffer(buffer);
    } finally {
        if (buffer != null) {
            buffer.release(); // 确保资源释放
        }
    }
});

监控与调优

1. 任务队列监控

实时监控任务队列状态,防止任务堆积:

public class QueueMonitor {
    private final SingleThreadEventExecutor executor;
    
    public void monitorQueue() {
        int pendingTasks = executor.pendingTasks();
        if (pendingTasks > 1000) {
            log.warn("Task queue is growing: {} tasks pending", pendingTasks);
        }
    }
}
2. 性能指标收集

收集关键性能指标用于调优:

指标名称描述建议阈值
平均任务处理时间单个任务的平均执行时间< 10ms
任务队列长度等待处理的任务数量< 1000
任务拒绝率因队列满被拒绝的任务比例< 1%
EventLoop利用率EventLoop线程的CPU使用率60-80%

高级任务调度模式

1. 条件任务执行

根据运行时条件决定是否执行任务:

public class ConditionalTask implements Runnable {
    private final Supplier<Boolean> condition;
    private final Runnable task;
    
    public ConditionalTask(Supplier<Boolean> condition, Runnable task) {
        this.condition = condition;
        this.task = task;
    }
    
    @Override
    public void run() {
        if (condition.get()) {
            task.run();
        }
    }
}

// 使用条件任务
eventLoop.execute(new ConditionalTask(
    () -> channel.isActive(), 
    () -> sendHeartbeat(channel)
));
2. 任务链式执行

构建任务执行流水线:

public class TaskChain {
    private final EventLoop eventLoop;
    private final List<Runnable> tasks = new ArrayList<>();
    
    public TaskChain(EventLoop eventLoop) {
        this.eventLoop = eventLoop;
    }
    
    public TaskChain addTask(Runnable task) {
        tasks.add(task);
        return this;
    }
    
    public void execute() {
        if (tasks.isEmpty()) return;
        
        Runnable firstTask = tasks.get(0);
        eventLoop.execute(() -> {
            firstTask.run();
            for (int i = 1; i < tasks.size(); i++) {
                final int index = i;
                eventLoop.execute(() -> tasks.get(index).run());
            }
        });
    }
}

// 使用任务链
new TaskChain(eventLoop)
    .addTask(() -> step1())
    .addTask(() -> step2())
    .addTask(() -> step3())
    .execute();

通过遵循这些最佳实践,可以充分发挥Netty事件循环模型的优势,构建出高性能、高可靠性的网络应用程序。关键是要理解Netty的线程模型特性,合理设计任务调度策略,并建立完善的监控和异常处理机制。

总结

Netty的事件循环模型通过精巧的Reactor模式实现,提供了高效、可扩展的网络编程基础框架。EventLoopGroup与EventLoop的清晰职责划分,单线程与多线程模型的灵活选择,以及任务调度与异步处理的最佳实践,共同构成了Netty高性能的基石。开发者应根据实际应用场景选择合适的线程模型,遵循最佳实践,充分利用Netty提供的性能优化特性,才能构建出高性能、高可靠性的网络应用程序。Netty的成功不仅在于其优秀的设计理念,更在于其在实际应用中所展现出的卓越性能和稳定性。

【免费下载链接】netty Netty project - an event-driven asynchronous network application framework 【免费下载链接】netty 项目地址: https://gitcode.com/gh_mirrors/ne/netty

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值