EventLoop 本质是一个单线程执行器(同时维护了一个 Selector),里面有 run 方法处理 Channel 上源源不断的 IO 事件。
package io.netty.channel;
import io.netty.util.concurrent.OrderedEventExecutor;
/**
* 注册后将处理 Channel 的所有 I/O 操作。
*
* 一个 EventLoop 实例通常会处理多个 Channel,但这可能取决于实现细节和内部结构。
*/
public interface EventLoop extends OrderedEventExecutor, EventLoopGroup {
@Override
EventLoopGroup parent();
}
它的继承关系比较复杂:
j.u.c.ScheduleExecutorServer
因此包含线程池中所有方法OrderedEventExecutor
boolean inEventLoop(Thread thread);
方法判断一个线程是否属于此 EventLoopEventLoopGroup parent();
方法来看自己属于哪个 EventLooppackage io.netty.channel;
import io.netty.util.concurrent.EventExecutorGroup;
/**
* 特殊的 EventExecutorGroup 允许注册在事件循环期间处理以供以后选择的 Channel。
*/
public interface EventLoopGroup extends EventExecutorGroup {
/**
* 返回下一个要使用的 EventLoop
*/
@Override
EventLoop next();
/**
* 用这个 EventLoop 注册一个 Channel。
* 注册完成后,返回的 ChannelFuture 将收到通知。
*/
ChannelFuture register(Channel channel);
/**
* 使用 thisEventLoop 注册一个 Channel。 注册完成后,传递的 ChannelFuture 将收到通知,并且也会返回。
*/
ChannelFuture register(Channel channel, ChannelPromise promise);
}
EventLoopGroup 是一组 EventLoop。Channel 一般会调用 EventLoopGroup 的 register
方法来绑定其中一个 EventLoop,后续这个 Channel 上的 IO 事件都由此 EventLoop 来处理(保证了 IO 事件处理时的线程安全)
iterator
接口提供遍历 EventLoop 的能力next
方法获取集合中下一个 EventLoop代码理解实现:
public class TestEventLoop {
public static void main(String[] args) {
// 1、创建时间循环组
EventLoopGroup group = new NioEventLoopGroup(2); // 处理IO事件、提交普通、定时任务
/** new NioEventLoopGroup() 默认构造的线程数
* Runtime.getRuntime().availableProcessors() 电脑 CPU 的核心数
* static {
* DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
* "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
* if (logger.isDebugEnabled()) {
* logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
* }
* }
*/
// 2、获取下一个事件循环对象
System.out.println(group.next()); // io.netty.channel.nio.NioEventLoop@6f2b958e
System.out.println(group.next()); // io.netty.channel.nio.NioEventLoop@1eb44e46
System.out.println(group.next()); // io.netty.channel.nio.NioEventLoop@6f2b958e (循环回第一个)
// 3、执行普通任务
// group.next().submit
group.next().execute(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[" + Thread.currentThread().getName() + "] ok");
});
System.out.println("[" + Thread.currentThread().getName() + "] main");
// 4、执行定时任务
// 固定频率执行任务 参数1: 任务对象,参数2:初始延时事件,参数3:间隔时间,参数4:时间单位
group.next().scheduleAtFixedRate(()->{
System.out.println("[" + Thread.currentThread().getName() + "] scheduleAtFixedRate");
}, 0, 1, TimeUnit.SECONDS);
}
}
服务器代码:
public class EventLoopServer {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override // msg ByteBuf
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("[" + Thread.currentThread().getName() +"] "+ buf.toString(Charset.defaultCharset()));
}
});
}
})
.bind(8888);
}
}
客户端代码:
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 ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
// 5、连接到服务器
.connect(new InetSocketAddress("localhost", 8888))
.sync()
.channel();
System.out.println(channel);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String msg = scanner.nextLine();
channel.writeAndFlush(msg);
}
}
}
运行测试
可以看到两个工人(EventLoop )轮流处理 Channel,但 工人(EventLoop ) 与 Channel 之间进行了绑定
一旦建立连接,Channel 就会和一个 NioEventLoopGroup 绑定,后续的请求都会由同一个 EventLoop 来处理。
分工细化1: 如前面例子中的 boss(只负责 ServerSocketChannel 上 accept 事件) 和 worker(只负责 SocketChannel 上的读写事件) 两个角色
new ServerBootstrap()
// 参数1: boss(只负责 ServerSocketChannel 上 accept 事件) 参数2: worker(只负责 SocketChannel 上的读写事件)
// 只会占用 参数1(boss)里面的一个线程, worker 初始化两个线程(只有两个worker)一个 worker 可以管理多个 Channel
.group(new NioEventLoopGroup(), new NioEventLoopGroup(2))
分工细化2: 单独处理耗时长的 handler
// 细化2:创建一个独立的 EventLoopGroup
EventLoopGroup group = new DefaultEventLoopGroup();
...
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter(){
@Override // msg ByteBuf
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("[" + Thread.currentThread().getName() +"] "+ buf.toString(Charset.defaultCharset()));
ctx.fireChannelRead(msg); // 将消息传递给下一个 handler
}
// 使用 group
}).addLast(group, "handler2", new ChannelInboundHandlerAdapter(){
@Override // msg ByteBuf
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("[" + Thread.currentThread().getName() +"] "+ buf.toString(Charset.defaultCharset()));
}
});
...
关键代码 io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead()
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
// 下一个 handler 的事件循环是否与当前的事件循环是同一个线程(EventLoop)
EventExecutor executor = next.executor(); // 返回下一个 handler 的 EventLoop
// 是,直接使用
if (executor.inEventLoop()) { // 当前 handler 中的线程,是否和 EventLoop 是同一个线程(EventLoop)
next.invokeChannelRead(m);
}
// 不是,将要执行的代码作为一个任务提交给下一个事件循环处理(换人)
else {
executor.execute(new Runnable() { /// executor 下一个 handler 线程(EventLoop)
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}
总结: 如果两个 handler 绑定的是同一个线程(EventLoop),那么就直接调用;否则,把要调用的代码封装为一个任务对象,由下一个 handler 的线程(EventLoop)来调用。