目录
- 开场白:话说 Netty 江湖
- 第一段:EventLoopGroup——“包工头”的角色
- 第二段:EventLoop——“身怀绝技的工人”
- 第三段:EventLoop 如何处理 I/O 事件、普通任务和定时任务
- 第四段:Handler 执行中如何换人?
- 总结:EventLoop 和 EventLoopGroup 的关系
🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!
🌟了解 Java的 NIO 请看 : NIO,看完你就懂了!
其他优质专栏: 【🎇SpringBoot】【🎉多线程】【🎨Redis】【✨设计模式专栏(已完结)】…等
如果喜欢作者的讲解方式,可以点赞收藏加关注,你的支持就是我的动力
✨更多文章请看个人主页: 码熔burning
接下来,咱们这就来一场 Netty 的“相声大会”,主角就是 EventLoop 和 EventLoopGroup 这俩活宝!保证你听完之后,不仅能明白它们是干啥的,还能自己上手耍两把。😎
开场白:话说 Netty 江湖
话说这 Netty 江湖,高手如云,但要说最核心的,还得是 EventLoop 和 EventLoopGroup 这哥俩。它们就像是 Netty 的“心脏”和“大脑”,负责处理各种“江湖事务”,让 Netty 这个“武林高手”能轻松应对各种挑战。💪
了解Netty请看:【Netty篇】幽默的讲解带你入门 Netty !建议收藏
第一段:EventLoopGroup——“包工头”的角色
-
EventLoopGroup 是啥?
你可以把 EventLoopGroup 想象成一个“包工头”,它手底下管着一群“工人”,也就是 EventLoop。它的主要职责是:
- 招兵买马: 创建并管理 EventLoop。 🐴
- 分配任务: 接收各种“任务”(比如客户端连接、数据读写),然后把这些任务分配给手下的 EventLoop 去处理。 📝
- 统筹全局: 负责整个线程池的生命周期管理,比如启动、关闭等等。 💼
-
EventLoopGroup 的种类
Netty 提供了两种常用的 EventLoopGroup(当然还有很多其他的,这里就不举例了):
- NioEventLoopGroup: 这是最常用的,基于 NIO(Non-blocking I/O)实现,适合处理高并发的网络应用。你可以把它想象成一群“身手敏捷的忍者”,擅长处理各种异步 I/O 事件。
- EpollEventLoopGroup: 在 Linux 系统上,如果安装了 epoll,可以使用这个,性能更好。你可以把它想象成“开了外挂的忍者”,速度更快,效率更高。 🚀
-
EventLoopGroup 的使用
// 创建一个 NioEventLoopGroup,包含 4 个 EventLoop EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 通常 bossGroup 线程数设置为 1 即可 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 默认线程数是 CPU 核心数 * 2 try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) // 设置 bossGroup 和 workerGroup .channel(NioServerSocketChannel.class) // 指定使用 NIO 的 ServerSocketChannel .childHandler(new ChannelInitializer<SocketChannel>() { // 设置 ChannelHandler @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new MyHandler()); // 添加自定义的 Handler } }); // 绑定端口,开始接收连接 ChannelFuture f = b.bind(8080).sync(); // 等待服务器 socket 关闭 f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); }
这段代码里,
bossGroup
负责处理客户端的连接请求,workerGroup
负责处理连接建立后的 I/O 事件。
第二段:EventLoop——“身怀绝技的工人”
-
EventLoop 是啥?
EventLoop 就像是 EventLoopGroup 手下的“工人”,每个 EventLoop 都是一个单线程执行器,负责:
- 监听 I/O 事件: 通过 Selector 监听 Channel 上的各种 I/O 事件,比如连接建立、数据可读、数据可写等等。 👂
- 处理 I/O 事件: 当有 I/O 事件发生时,EventLoop 会负责读取数据、写入数据,并调用 ChannelHandler 进行业务处理。 🛠️
- 执行普通任务: 除了 I/O 事件,EventLoop 还可以执行一些普通的任务,比如用户自定义的业务逻辑。 👨💻
- 执行定时任务: EventLoop 还可以执行定时任务,比如定时发送心跳包、定时清理过期数据等等。 ⏰
-
EventLoop 的特点
- 单线程: 每个 EventLoop 都是一个单线程,这意味着所有任务都是串行执行的,避免了多线程并发带来的锁竞争问题。 🧵
- 非阻塞: EventLoop 通过 NIO 的 Selector 监听 I/O 事件,当没有事件发生时,线程会阻塞在 Selector 上,不会占用 CPU 资源。 😴
- 事件驱动: EventLoop 通过事件驱动的方式处理 I/O 事件,当有事件发生时,才会执行相应的处理逻辑。 🚦
-
EventLoop 的使用
EventLoop 的使用通常是隐式的,你不需要直接操作 EventLoop,Netty 会自动把任务分配给 EventLoop 去执行。
// 在 ChannelHandler 中执行任务 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 提交一个任务给 EventLoop 执行 ctx.executor().execute(() -> { try { Thread.sleep(1000); System.out.println("处理耗时任务:" + msg); ctx.writeAndFlush(msg); } catch (InterruptedException e) { e.printStackTrace(); } }); }
这段代码里,
ctx.executor()
返回的就是当前 Channel 绑定的 EventLoop,你可以通过execute()
方法提交一个任务给 EventLoop 执行。
第三段:EventLoop 如何处理 I/O 事件、普通任务和定时任务
-
处理 I/O 事件
- 监听事件: EventLoop 通过 Selector 监听 Channel 上的 I/O 事件。 📡
- 事件就绪: 当有 I/O 事件就绪时,Selector 会通知 EventLoop。 🔔
- 读取事件: EventLoop 从 Selector 中读取就绪的 I/O 事件。 📚
- 处理事件: EventLoop 根据事件类型,执行相应的处理逻辑,比如读取数据、写入数据、调用 ChannelHandler 等等。 ⚙️
-
处理普通任务
你可以通过
EventLoop.execute()
方法提交一个普通任务给 EventLoop 执行。EventLoop 会把这些任务放入一个队列中,然后按照 FIFO(先进先出)的顺序执行。 ➡️NioEventLoopGroup nioWorkers = new NioEventLoopGroup(2); log.debug("server start..."); Thread.sleep(2000); nioWorkers.execute(()->{ log.debug("normal task..."); });
输出:
22:30:36 [DEBUG] [main] c.i.o.EventLoopTest2 - server start... 22:30:38 [DEBUG] [nioEventLoopGroup-2-1] c.i.o.EventLoopTest2 - normal task...
可以用来执行耗时较长的任务。
-
处理定时任务
你可以通过
EventLoop.schedule()
或EventLoop.scheduleAtFixedRate()
方法提交一个定时任务给 EventLoop 执行。EventLoop 会把这些定时任务放入一个定时任务队列中,然后按照时间顺序执行。// 提交一个定时任务给 EventLoop 执行 ctx.executor().schedule(() -> { System.out.println("定时任务执行了"); ctx.writeAndFlush("心跳包"); }, 5, TimeUnit.SECONDS);
第四段:Handler 执行中如何换人?
在 Netty 的 ChannelPipeline 中,Handler 的执行顺序是按照添加的顺序依次执行的。但是,在 Handler 的执行过程中,你可以通过以下方式“换人”:
ctx.fireChannelRead(msg)
: 这个方法会把消息传递给 Pipeline 中的下一个 InboundHandler。你可以把它想象成“传球”,把消息传递给下一个 Handler 去处理。 ⚽ctx.writeAndFlush(msg)
: 这个方法会把消息传递给 Pipeline 中的 OutboundHandler,然后把数据写入 Channel。你可以把它想象成“射门”,把消息发送出去。 🥅ctx.pipeline().remove(handler)
: 这个方法会从 Pipeline 中移除指定的 Handler。你可以把它想象成“换人”,把不需要的 Handler 移除掉。 ❌ctx.pipeline().replace(oldHandler, newHandler)
: 这个方法会用新的 Handler 替换旧的 Handler。你可以把它想象成“换人”,用新的 Handler 替换旧的 Handler。 🔄
总结:EventLoop 和 EventLoopGroup 的关系
EventLoopGroup 就像是一个“包工头”,负责创建和管理 EventLoop。EventLoop 就像是“工人”,负责处理 I/O 事件、普通任务和定时任务。它们之间的关系就像是“领导”和“员工”,互相配合,共同完成任务。🤝
结尾:Netty 江湖,等你来闯
好了,今天的 Netty “相声大会”就到这里。希望通过这种幽默的方式,让你对 EventLoop 和 EventLoopGroup 有了更深入的了解。Netty 江湖,等你来闯! 🎉
补充说明:
- 线程模型: Netty 的线程模型是 Reactor 模式,EventLoop 就是 Reactor 模式中的 Reactor。 ⚛️
- 性能优化: 合理配置 EventLoopGroup 的线程数,可以提高 Netty 的性能。通常情况下,
bossGroup
的线程数设置为 1 即可,workerGroup
的线程数设置为 CPU 核心数 * 2。 📈 - 异常处理: 在 ChannelHandler 中,一定要注意处理异常,避免因为一个 Handler 出现异常而导致整个 Pipeline 崩溃。 ⚠️