1-组件类比理解
channel 理解为数据的通道
msg 理解为流动的数据,最开始输入是 ByteBuf,但经过 pipeline 的加工,会变成其它类型对象,最后输出又变成 ByteBuf
handler 理解为数据的处理工序
- 工序有多道,合在一起就是 pipeline,pipeline 负责发布事件(读、读取完成...)传播给每个 handler, handler 对自己感兴趣的事件进行处理(重写了相应事件处理方法)
- handler 分 Inbound 和 Outbound 两类
把 eventLoop 理解为处理数据的工人
- 工人可以管理多个 channel 的 io 操作,并且一旦工人负责了某个 channel,就要负责到底(绑定)
- 工人既可以执行 io 操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个 channel 的待处理任务,任务分为普通任务、定时任务
- 工人按照 pipeline 顺序,依次按照 handler 的规划(代码)处理数据,可以为每道工序指定不同的工人
2-常用的EventLoop
EventLoop 本质是一个单线程执行器(同时维护了一个 Selector),里面有 run 方法处理 Channel 上源源不断的 io 事件。
它的继承关系比较复杂
- 一条线是继承自 j.u.c.ScheduledExecutorService 因此包含了线程池中所有的方法
- 另一条线是继承自 netty 自己的 OrderedEventExecutor,
- 提供了 boolean inEventLoop(Thread thread) 方法判断一个线程是否属于此 EventLoop
- 提供了 parent 方法来看看自己属于哪个 EventLoopGroup
EventLoopGroup group1 = new NioEventLoopGroup();不传递参数,线程数就当前计算cpu 核数 *2
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
private static final int DEFAULT_EVENT_LOOP_THREADS;
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
}
事件循环组
EventLoopGroup 是一组 EventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,
后续这个 Channel 上的 io 事件都由此 EventLoop 来处理(保证了 io 事件处理时的线程安全)
- 继承自 netty 自己的 EventExecutorGroup
- 实现了 Iterable 接口提供遍历 EventLoop 的能力
- 另有 next 方法获取集合中下一个 EventLoop
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.EventExecutor;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class EventLoopTest {
public static void main(String[] args) {
//DefaultEventLoopGroup:执行普通任务和定时任务;NioEventLoopGroup:执行普通任务和定时任务+IO事件
EventLoopGroup group = new DefaultEventLoopGroup();
EventLoopGroup group1 = new NioEventLoopGroup();
for (EventExecutor eventLoop : group1) {
System.out.println(eventLoop);//当前服务器4核,所以输出8次
}
// 执行普通任务
group.next().execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("ok");
});
// 执行定时任务
group.next().scheduleAtFixedRate(() -> {
log.debug("ok");
}, 0, 1, TimeUnit.SECONDS);
log.debug("main");
}
}
3-调试EventLoopGroup和channel绑定关系
3.1-单个channel多次发送消息
结论1:单个客户端channel发送消息,服务端始终用同一个EventLoopGroup来处理;
EventLoopServer.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class EventLoopServer {
public static void main(String[] args) {
//创建一个独立的普通DefaultEventLoopGroup 类型 EventLoopGroup,可以专门处理耗时操作,避免NioEventLoopGroup处理耗时操作,影响系统性能
EventLoopGroup group = new DefaultEventLoopGroup();
//bossGroup 只负责 ServerSocketChannel 上 accept 事件,一般设置为1
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
//workGroup 只负责 socketChannel 上的读写,这里为了演示效果设置为2,实际项目根据自己的服务器状况设置
NioEventLoopGroup workGroup = new NioEventLoopGroup(2);
new ServerBootstrap()
.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter() {
@Override // ByteBuf
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug(buf.toString(Charset.defaultCharset()));
}
});
}
})
.bind(8080);
}
}
EventLoopClient.java
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
@Slf4j
public class EventLoopClient {
public static void main(String[] args) throws InterruptedException {
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override // 在连接建立后被调用
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost", 8080));
channelFuture.sync(); // 阻塞住当前线程,直到nio线程连接建立完毕
Channel channel = channelFuture.channel();
log.debug("channel={}", channel);
log.debug("################");
}
}
3.2-多个channel发送消息
代码还是上面的代码,客户端启动多个,启动方式参见https://blog.csdn.net/ycmy2017/article/details/128777903 中的调试
结论:一个EventLoopGroup可以处理多个channel,并且如果EventLoopGroup处理了哪个channel,后续一直由当前的EventLoopGroup处理这个channel....
描述:client1发送了11,12,13;client2发送了21,22,23;client3发送了31;
现象:我们看到服务端,NioEventLoopGroup workGroup = new NioEventLoopGroup(2);NioEventLoopGroup参数传递2,服务端NioEventLoopGroup只有2个,客户端3个,其中client1交给nioEventLoopGroup-4-1处理了,client2交给nioEventLoopGroup-4-2处理了,client3又交给nioEventLoopGroup-4-1处理了...
3.3-多个channel和多个EventLoopGroup
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class EventLoopServer {
public static void main(String[] args) {
//创建一个独立的普通DefaultEventLoopGroup 类型 EventLoopGroup,可以专门处理耗时操作,避免NioEventLoopGroup处理耗时操作,影响系统性能
EventLoopGroup group = new DefaultEventLoopGroup();
//bossGroup 只负责 ServerSocketChannel 上 accept 事件,一般设置为1
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
//workGroup 只负责 socketChannel 上的读写,这里为了演示效果设置为2,实际项目根据自己的服务器状况设置
NioEventLoopGroup workGroup = new NioEventLoopGroup(2);
new ServerBootstrap()
.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter() {
@Override // ByteBuf
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug("handler1,msg={}",buf.toString(Charset.defaultCharset()));
ctx.fireChannelRead(msg); // 让消息传递给下一个handler
}
})
.addLast(group, "handler2", new ChannelInboundHandlerAdapter() {
@Override // ByteBuf
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
Thread.sleep(2000L);//这里模拟另外一个需要耗时很久,模拟2s
log.debug("handler2,msg={}",buf.toString(Charset.defaultCharset()));
}
});
}
})
.bind(8080);
}
}
描述:client1发送了1001,1002,1003;client2发送了2001,2002;client3发送了3001,3002,3003;EventLoopGroup group = new DefaultEventLoopGroup();系统4核,所以这里有8个可以处理;因此defaultEventLoopGroup会有defaultEventLoopGroup-2-1,defaultEventLoopGroup-2-2,defaultEventLoopGroup-2-3
4-ChannelFuture
connect是个异步方法,所以 channelFuture.sync()阻塞;
// 2. 带有 Future,Promise 的类型都是和异步方法配套使用,用来处理结果
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override // 在连接建立后被调用
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
// 1. 连接到服务器
// 异步非阻塞, main线程 发起了调用,真正执行 connect 是 nio 线程
.connect(new InetSocketAddress("localhost", 8080)); // 1s 秒后
log.debug("channel1={}", channelFuture.channel());
channelFuture.sync(); // 阻塞住当前线程,直到nio线程连接建立完毕
Channel channel = channelFuture.channel();
log.debug("channel2={}", channel);
log.debug("################");
还可以采用回调的方式进行
// 2. 带有 Future,Promise 的类型都是和异步方法配套使用,用来处理结果
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override // 在连接建立后被调用
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
// 1. 连接到服务器
// 异步非阻塞, main线程 发起了调用,真正执行 connect 是 nio 线程
.connect(new InetSocketAddress("localhost", 8080)); // 1s 秒后
log.debug("channel1={}", channelFuture.channel());
// 2.2 使用 addListener(回调对象) 方法异步处理结果
channelFuture.addListener(new ChannelFutureListener() {
@Override
// 在 nio 线程连接建立好之后,会调用 operationComplete
public void operationComplete(ChannelFuture future) throws Exception {
Channel channel = future.channel();
log.debug("channel2={}", channel);
// channel.writeAndFlush("hello, world");
}
});
本文深入解析Netty中EventLoop的工作原理,包括EventLoop、EventLoopGroup的角色与职责,以及它们如何与Channel协同工作实现高效的I/O处理。通过具体示例展示了不同场景下EventLoop的绑定关系。

2808

被折叠的 条评论
为什么被折叠?



