Netty入门-组件EventLoop
3、组件
3.1、EventLoop
事件循环对象EventLop
EventLoop 本质是一个单线程执行器(同时维护了一个 Selector),里面有 run 方法处理 Channel 上源源不断的 io 事件。
继承关系:
- 一条线是继承自 j.u.c.ScheduledExecutorService 因此包含了线程池中所有的方法
- 另一条线是继承自 netty 自己的 OrderedEventExecutor,
- 提供了 boolean inEventLoop(Thread thread) 方法判断一个线程是否属于此 EventLoop
- 提供了 parent 方法来看看自己属于哪个 EventLoopGroup
事件循环组EventLoopGroup
EventLoopGroup 是一组 EventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,后续这个 Channel 上的 io 事件都由此 EventLoop 来处理(保证了 io 事件处理时的线程安全)
继承自 netty 自己的 EventExecutorGroup
- 实现了 Iterable 接口提供遍历 EventLoop 的能力
- 另有 next 方法获取集合中下一个 EventLoop
代码
@Slf4j
public class TestEventLoop {
public static void main(String[] args) {
//1. 创建事件循环组
EventLoopGroup group = new NioEventLoopGroup(2);//io 事件,普通任务,定时恩物
EventLoopGroup group1 = new DefaultEventLoopGroup();//普通任务,定时任务
//2. 循环获取事件对象,轮询,1和3一样
System.out.println(group.next());//1
System.out.println(group.next());//2
System.out.println(group.next());//3
//3. 执行普通任务,异步的
group.next().submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("普通任务..");
});
log.debug("main..");
//4. 执行定时任务
group.next().scheduleAtFixedRate(() -> {
log.debug("定时任务..");
}, 0, 1, TimeUnit.SECONDS);
}
}
IO任务代码
//服务端
//**.group方法建议指定两个EventLoopGroup,第一个就是boss,第二个就是worker**
@Slf4j
public class EventLoopServer {
public static void main(String[] args) {
new ServerBootstrap()
//.group(new NioEventLoopGroup())
.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override//此处没有添加解码器,所以msg还是ByteBuf类型
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug(buf.toString(Charset.defaultCharset()));
}
});
}
})
.bind(8080);
}
}
//客户端
public class EventLoopClient {
public static void main(String[] args) throws InterruptedException {
//1. 启动类
Channel channel = new Bootstrap()
//2. 添加EventLoop
.group(new NioEventLoopGroup())
//3. 选择客户端channel实现
.channel(NioSocketChannel.class)
//4. 添加处理器
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
//在连接建立后被调用
protected void initChannel(NioSocketChannel channel) throws Exception {
//编码器,将要发送的消息转为ByteBuf
channel.pipeline().addLast(new StringEncoder());
}
})
//5. 连接到服务器
.connect(new InetSocketAddress(8080))
//阻塞方法,直到连接建立
.sync()
//代表连接对象
.channel();
System.out.println(channel);
System.out.println("=====");//此处加断点
}
}
客户端加断点的地方注意点,
需要由All改为Thread,因为netty客户端主线程和发数据的线程并不是同一个线程。
开启多个客户端进入debug模式,发现每个客户端都与一个EventLoop进行了绑定,工人(EventLoop)与channel进行了绑定。
EventLoop与channel是一对多的关系
优化
-
1.EventLoopGroup分工细化
//第一个参数就是boss,第二个参数就是worker .group(new NioEventLoopGroup(), new NioEventLoopGroup())
为了测试多个channel可以共用一个EventLoop,可以将worker线程数据进行手动new NioEventLoopGroup(2),代表只有两个worker,然后启动三个以上客户端,debug测试,观察结果,发现client-0和client-2共用了EventLoop-3-1,client与EventLoop就是多对一的关系
-
2.如果处理消息过于复杂,耗时,就会影响netty内的EventLoop,改进:添加自定义的EvnetLoop,将消息从netty内传递到自定义的EventLoop处理
@Slf4j public class EventLoopServer { public static void main(String[] args) { EventLoopGroup group = new DefaultEventLoopGroup(); new ServerBootstrap() //.group(new NioEventLoopGroup()) //细化1:boss和worker分开 .group(new NioEventLoopGroup(), new NioEventLoopGroup(2)) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel channel) throws Exception { channel.pipeline() .addLast("handler1", new ChannelInboundHandlerAdapter() { @Override//此处没有添加解码器,所以msg还是ByteBuf类型 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; log.debug("传递前:"+buf.toString(Charset.defaultCharset())); ctx.fireChannelRead(msg); } }) //细化2:添加自定义handler,group .addLast(group, "handler2", new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; log.debug("传递后:"+buf.toString(Charset.defaultCharset())); } }); } }) .bind(8080); } }
传递前:用的nioEventLoop,传递后用的自定义的defaultEventLoopGroup,而defaultEventLoopGroup没有指定线程数,就是CPU核心数*2,三个客户端轮询使用nioEventLoopGroup
handler执行中如何换人
源码:io.netty.channel.AbstractChannelHandlerContext
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
//executor方法返回的是下一个handler的eventLoop,EventExecutor是EventLoop的父类,所以他两是一个东西
EventExecutor executor = next.executor();
//如果当前handler的线程与下一个eventLoop是用一个线程,是就直接调用
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
//否则,将要执行的代码作为任务提交给下一个时间循环处理(换人)
//execute(new Runnable() 方法就是开启了一个新线程,而这个线程及时nex的线程
} else {
executor.execute(new Runnable() {
public void run() {
next.invokeChannelRead(m);
}
});
}
}
- 如果两个 handler 绑定的是同一个线程,那么就直接调用
- 否则,把要调用的代码封装为一个任务对象,由下一个 handler 的线程来调用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南