一、EventLoop - 事件循环对象
EventLoop
EventLoop 本质是一个单线程,维护了一个selector,处理多个channel上的各种IO事件。
继承关系:
- 继承自j.u.c.ScheduledExecutorService,因此包含了线程池所有的方法
- 继承自netty提供的 OrderedEventExecutor
- 提供 boolean inEventLoop(Thread thread); 判断一个线程是否属于此EventLoop
- 提供 EventExecutorGroup parent(); 获取自己数据哪个EventLoopGroup
EventLoopGroup
EventLoopGroup是一组EventLoop,Channel会调用EventLoopGroup的 ChannelFuture register(Channel channel);方法,同EventLoopGroup中的某个EventLoop进行绑定,这样channel中的全部io时间都由这个EventLoop进行处理,保证线程安全。
继承关系:
- 继承 Iterable<EventExecutor> 提供了对EventLoop的迭代方法
- 提供 EventExecutor next();获取下一个EventLoop
、EventLoop的简单使用
package com.test.netty.c2;
import io.netty.channel.DefaultEventLoop;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.NettyRuntime;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class TestEventLoop {
public static void main(String[] args) {
//1、创建时间循环组
EventLoopGroup group = new NioEventLoopGroup(2);//io事件、普通任务、定时任务
//EventLoopGroup group = new DefaultEventLoop();//普通任务、定时任务
//2、获取下一个事件循环对象 负载均衡
System.out.println(group.next());
System.out.println(group.next());
System.out.println(group.next());
//3、执行普通任务,异步处理\代码执行权
group.next().submit(()->{
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
log.debug("ok");
});
//4、定时任务
group.next().scheduleAtFixedRate(()->{
log.debug("定时任务");
}, 0,3, TimeUnit.SECONDS);
log.debug("main");
}
}
关闭EventLoopGroup使用 group.shutdownGracefully(); 方法,这个方法就是非强制性关闭,先拒绝其他channel的注册,等待io事件全部执行完成,最后关闭。
二、实例
服务器端:
package com.test.netty.c2;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
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) {
//细分第二部:创建一个独立的eventLoop,处理耗时长的任务
EventLoopGroup group = new DefaultEventLoop();
new ServerBootstrap()
//职责划分更细 boss、worker
//第一个参数 boss 只负责 ServerSocketChannel accept之间
//第二个参数 worker socketChannel 负责读写事件
.group(new NioEventLoopGroup(), new NioEventLoopGroup(2))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast("handler-1", new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
log.debug(byteBuf.toString(Charset.defaultCharset()));
//将消息传递给下一个handler
ctx.fireChannelRead(msg);
}
}).addLast(group, "handler-2", new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
log.debug(byteBuf.toString(Charset.defaultCharset()));
}
});;
}
}).bind(8080);
}
}
客户端:
package com.test.netty.c2;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
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 java.net.InetSocketAddress;
public class EventLoopClient {
public static void main(String[] args) throws Exception {
//1、启动类
Channel channel = new Bootstrap()
//2、添加EventLoop
.group(new NioEventLoopGroup())
//3、选择客户端channel实现
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
//建立连接后调用
channel.pipeline().addLast(new StringEncoder());
}
})
//5、连接到服务器
.connect(new InetSocketAddress("127.0.0.1", 8080))
.sync()
.channel();
System.in.read();
}
}
EventLoop分工
在ServerBootstrap 的 group在初始化的时候,可以传递两个EvenLoopGroup的实现类如下:
.group(new NioEventLoopGroup(), new NioEventLoopGroup(2))
这种情况下,channel的事件就会区分到两个EventLoopGroup进行处理,如同之前的boss线程和worker线程:
- 第一个参数 boss 只负责 ServerSocketChannel accept之间
- 第二个参数 worker socketChannel 负责读写事件
注册的时候,可以执行线程个数,因为ServerSocketChannel中有且只有一个线程会监听accept,所以不用指定线程个数。同时可以知道,一个线程如果同channel绑定,那么之后这个channel所有的io事件都由这个线程负责。
如果出现了一个耗时比较长的操作,可能导致这个EventLoop上的其他channel都会阻塞,可以为耗时的handler指定一个EventLoop,使用DefaultEventLoop:
这里可以看到,handler-2输出的数据是另一个线程:
切换的实现
下面是切换EventLoopGroup,切换的实现原理如下:
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
// 获得下一个EventLoop, excutor 即为 EventLoopGroup
EventExecutor executor = next.executor();
// 如果下一个EventLoop 在当前的 EventLoopGroup中
if (executor.inEventLoop()) {
// 使用当前 EventLoopGroup 中的 EventLoop 来处理任务
next.invokeChannelRead(m);
} else {
// 否则让另一个 EventLoopGroup 中的 EventLoop 来创建任务并执行
executor.execute(new Runnable() {
public void run() {
next.invokeChannelRead(m);
}
});
}
}
当两个handler绑定的是同一个EventLoopGroup,那么直接调用,否则把调用的代码交给下一个handler的EventLoopGroup来调用。