本文适合有netty使用经验者且对于nio有基本了解的读者
Netty介绍
java中NIO的使用相对复杂,开发工作量和难度较大,对于入门开发者来说难度较大,开发者除了要关心业务逻辑外,还要处理较为复杂的网络问题,例如客户端面临断线重连、 网络闪断、心跳处理、半包读写、 网络拥塞和异常流的处 理等等。
Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 帮你解决了上述问题可以确保你快速和简单的开发出一个网络应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。
许多开源的项目都是基于netty开发的,例如大名鼎鼎的阿里分布式框架Dubbo的RPC协议默认就是用netty实现通信的。
netty相关的开源项目:https://netty.io/wiki/related-projects.html
Netty的线程模型
该模型设计要点
- Netty 抽象出两组线程池BossGroup和WorkerGroup,BossGroup专门负责接收客户端的连接, WorkerGroup专 门负责网络的读写
- BossGroup和WorkerGroup类型都是NioEventLoopGroup
- NioEventLoopGroup 相当于一个事件循环线程组, 这个组中含有多个事件循环线程 , 每一个事件循环线程是 NioEventLoop
- 每个NioEventLoop都有一个selector (就是nio的selector), 用于监听注册在其上的socketChannel(nio的socketChannel)的网络通讯
- 每个Boss NioEventLoop线程内部循环执行的步骤有 3 步
处理accept事件 , 与client 建立连接 , 生成 NioSocketChannel
将NioSocketChannel注册到某个worker NIOEventLoop上的selector
处理任务队列的任务 , 即runAllTasks - 每个worker NIOEventLoop线程循环执行的步骤
轮询注册到自己selector上的所有NioSocketChannel 的read, write事件
处理 I/O 事件, 即read , write 事件, 在对应NioSocketChannel 处理业务
runAllTasks处理任务队列TaskQueue的任务 ,一些耗时的业务处理一般可以放入TaskQueue中慢慢处
理,这样不影响数据在 pipeline 中的流动处理 - 每个worker NIOEventLoop处理NioSocketChannel业务时,会使用 pipeline (管道),管道中维护了很多 handler 处理器用来处理 channel 中的数据,我们基于netty开发时,实际上就是开发我们自定义的handler进行数据处理
Netty的简单使用
netty入门非常简单,我们只需要实现自己的handler就可以了,附上一个简单的netty server demo
server
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) {
channel.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("netty server start.");
ChannelFuture cf = serverBootstrap.bind(9000).sync();
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
自定义handler
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 连接成功后回调
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
String send = "[ 客户端 ]" + channel.remoteAddress() + " 上线了 " + format.format(new Date()) + "\n";
channelGroup.writeAndFlush(Unpooled.copiedBuffer(send.getBytes(StandardCharsets.UTF_8)));
channelGroup.add(channel);
System.out.println(channel.remoteAddress() + "上线了" + "\n");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
String send = "[ 客户端 ]" + channel.remoteAddress() + "下线了" + "\n";
channelGroup.writeAndFlush(Unpooled.copiedBuffer(send.getBytes(StandardCharsets.UTF_8)));
System.out.println("当前在线人数:" + channelGroup.size());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
String data = buf.toString(CharsetUtil.UTF_8);
Channel channel = ctx.channel();
if (data.startsWith("88\r\n")) {
channelGroup.forEach(channel1 -> {
if (channel != channel1) {
String send = "[ 客户端 ]" + channel.remoteAddress() + "说:" + data + "\n";
channel1.writeAndFlush(Unpooled.copiedBuffer(send.getBytes(StandardCharsets.UTF_8)));
} else {
String send = "[ 自己 ]说:" + data + "\n";
channel.writeAndFlush(Unpooled.copiedBuffer(send.getBytes(StandardCharsets.UTF_8)));
}
});
System.out.println("client close: " + channel.remoteAddress());
channel.writeAndFlush(Unpooled.copiedBuffer("886\n".getBytes(StandardCharsets.UTF_8)));
channel.close();
} else {
channelGroup.forEach(channel1 -> {
if (channel != channel1) {
String send = "[ 客户端 ]" + channel.remoteAddress() + "说:" + data + "\n";
channel1.writeAndFlush(Unpooled.copiedBuffer(send.getBytes(StandardCharsets.UTF_8)));
} else {
String send = "[ 自己 ]说:" + data + "\n";
channel.writeAndFlush(Unpooled.copiedBuffer(send.getBytes(StandardCharsets.UTF_8)));
}
});
}
}
}
server启动后我们借助telnet工具模拟客户端访问
Netty server启动源码剖析
netty server启动的关键类是ServerBootstrap,我们对ServerBootstrap进行了一系列的初始化赋值,随后调用了serverBootstrap.bind(9000)去启动netty server。先来拆解一下看看我们初始化了什么
我们先是new了两个事件循环组
NioEventLoopGroup 由一系列的NioEventLoop 组成,每个NioEventLoop都会有一个异步线程,一个selector 和taskQueue,详见上面Netty的线程模型。
bossGroup 用来处理连接事件,这个事件处理逻辑非常简单就是把新的连接建立好后注册到workerGroup中,所以我们通常都是指定为一个线程处理,当然如果你的机器非常牛逼也可以指定多个线程,不过这就需要指定多个端口了。
workerGroup 我们没有指定线程数,默认会是服务器逻辑核数的两倍,或者你可以通过-Dio.netty.eventLoopThreads参数进行配置。
ServerBootstrap#group(io.netty.channel.EventLoopGroup, io.netty.channel.EventLoopGroup)
将我们的bossGroup放到了父类的 group 属性中,workerGroup放到了childGroup 属性中
AbstractBootstrap#channel
将我们指定的NioServerSocketChannel.class放到了channelFactory中,后续会用来创建该对象
AbstractBootstrap#option
这是用来做一些自定义网络配置的例如demo中配置了SO_BACKLOG,初始化服务器连接队列大小,多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
ServerBootstrap#childHandler(io.netty.channel.ChannelHandler)
这里配置了我们自定义的ChannelInitializer,当一个新的客户端连接被建立时,会回调它的initChannel函数,对新的连接进行我们的自定义配置处理,简单的理解就是可以往每个channel的pipline中添加我们的业务handler。
这里顺便额外提一嘴,读者能理解就理解一下,不能理解的话后面看完源码再回过头来理解,每个连接channel都会生成自己的handler,所以每个handler处理成员变量的时候是线程安全的(例如解决拆包粘包问题时我们在handler中定义的用来计算收包长度的成员变量,它不会有并发问题)
上面就是我们写的一系列赋值配置,如果不确定的地方可以debug确认一下
下面是我对关键代码serverBootstrap.bind(9000)的剖析,以流程图的方式展示
以上是我对netty线程模型以及启动源码的剖析,下面我们聊聊netty使用过程中遇到的一些问题,以及技术要点
Netty拆包粘包问题解决
TCP是一个流协议,就是没有界限的一长串二进制数据。TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区 的实际情况进行数据包的划分,所以在业务上认为是一个完整的包,可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成 一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。面向流的通信是无消息保护边界的。
如下图所示,client发了两个数据包D1和D2,但是server端可能会收到如下几种情况的数据。
解决方案
- 消息定长度,传输的数据大小固定长度,例如每段的长度固定为100字节,如果不够空位补空格
- 在数据包尾部添加特殊分隔符,比如下划线,中划线等,这种方法简单易行,但选择分隔符的时候一定要注意每条数据的内部一定不 能出现分隔符。
- 发送长度:发送每条数据的时候,将数据的长度一并发送,比如可以选择每条数据的前4位是数据的长度,应用层处理时可以根据长度 来判断每条数据的开始和结束。
Netty提供了多个解码器,可以进行分包的操作,如下:
LineBasedFrameDecoder (回车换行分包)
DelimiterBasedFrameDecoder(特殊分隔符分包)
FixedLengthFrameDecoder(固定长度报文来分包)
以上这些方式其实是存在一些缺陷的,最完美的解决方案是通过发送长度来解析数据包,下面附上一个简单的demo
关键类 SplitMessage 用来封装我们传输的数据包
public class SplitMessage {
private int len;
private byte[] msg;
public int getLen() {
return len;
}
public void setLen(int len) {
this.len = len;
}
public byte[] getMsg() {
return msg;
}
public void setMsg(byte[] msg) {
this.msg = msg;
}
@Override
public String toString() {
return "SplitMessage{" +
"len=" + len +
", msg=" + new String(msg, StandardCharsets.UTF_8) +
'}';
}
}
关键类 SplitEncoder 出站处理,用来处理上层业务handler发送过来的SplitMessage包,先将包长度写入buf再将数据包写入buf
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class SplitEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
if (o instanceof SplitMessage) {
SplitMessage msg = (SplitMessage)o;
byteBuf.writeInt(msg.getLen());
byteBuf.writeBytes(msg.getMsg());
} else {
throw new IllegalArgumentException();
}
}
}
关键类 SplitDecoder 进站处理,先读取4个字节用来判断接收包长度,再往后读取上面读取到的数据长度,数据长度不够则return等待。
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class SplitDecoder extends ByteToMessageDecoder {
private int len = 0;
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
if (byteBuf.readableBytes() >= 4) {
if (len == 0) {
len = byteBuf.readInt();
}
if (byteBuf.readableBytes() < len) {
System.out.println("发现拆包,等待数据。");
return;
}
if (len > 0) {
byte[] msg = new byte[len];
if (byteBuf.readableBytes() >= len) {
byteBuf.readBytes(msg);
SplitMessage splitMessage = new SplitMessage();
splitMessage.setLen(len);
splitMessage.setMsg(msg);
System.out.println("收到消息:" + new String(msg, StandardCharsets.UTF_8));
list.add(splitMessage);
}
}
len = 0;
}
}
}
在我们的server和client中加入这两handler
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("decoder", new SplitDecoder());
pipeline.addLast("encoder", new SplitEncoder());
pipeline.addLast(new NettyServerHandler());
}
});
clientBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("decoder", new SplitDecoder());
pipeline.addLast("encoder", new SplitEncoder());
pipeline.addLast(new NettyClientHandler());
}
});
客户端写数据时将数据封装成splitMessage再发送
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class NettyClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap clientBootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("decoder", new SplitDecoder());
pipeline.addLast("encoder",new SplitEncoder());
pipeline.addLast(new NettyClientHandler());
}
});
ChannelFuture channelFuture = clientBootstrap.connect("127.0.0.1", 9000).sync();
Channel channel = channelFuture.channel();
System.out.println("=======" + channel.localAddress() + "=======");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String msg = scanner.nextLine();
// 封装消息后再传输
SplitMessage splitMessage = new SplitMessage();
byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);
System.out.println(msg);
splitMessage.setMsg(bytes);
splitMessage.setLen(bytes.length);
if (channel.isActive()) {
channel.writeAndFlush(splitMessage);
} else {
throw new IOException("channel has been close.");
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
server 的上层handler在接收到数据时通过splitMessage获取
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
public class NettyServerHandler extends SimpleChannelInboundHandler<SplitMessage> {
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 连接成功后回调
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
String msg = "[ 客户端 ]" + channel.remoteAddress() + " 上线了 " + format.format(new Date()) + "\n";
SplitMessage splitMessage = new SplitMessage();
splitMessage.setMsg(msg.getBytes(StandardCharsets.UTF_8));
splitMessage.setLen(msg.getBytes(StandardCharsets.UTF_8).length);
channelGroup.writeAndFlush(splitMessage);
channelGroup.add(channel);
System.out.println(channel.remoteAddress() + "上线了" + "\n");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
String msg = "[ 客户端 ]" + channel.remoteAddress() + "下线了" + "\n";
SplitMessage splitMessage = new SplitMessage();
splitMessage.setMsg(msg.getBytes(StandardCharsets.UTF_8));
splitMessage.setLen(msg.length());
channelGroup.writeAndFlush(splitMessage);
System.out.println("当前在线人数:" + channelGroup.size());
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, SplitMessage splitMessage) throws Exception {
Channel channel = channelHandlerContext.channel();
channelGroup.forEach(channel1 -> {
if (channel != channel1) {
String msg = "[ 客户端 ]" + channel.remoteAddress() + "说:" + new String(splitMessage.getMsg(),StandardCharsets.UTF_8) + "\n";
SplitMessage splitMessage1 = new SplitMessage();
splitMessage1.setMsg(msg.getBytes(StandardCharsets.UTF_8));
splitMessage1.setLen(msg.getBytes(StandardCharsets.UTF_8).length);
channel1.writeAndFlush(splitMessage1);
} else {
String msg = "[ 自己 ]说:" + new String(splitMessage.getMsg(),StandardCharsets.UTF_8) + "\n";
splitMessage.setMsg(msg.getBytes(StandardCharsets.UTF_8));
splitMessage.setLen(msg.getBytes(StandardCharsets.UTF_8).length);
channel.writeAndFlush(splitMessage);
}
});
}
}
Netty零拷贝
Netty的接收和发送ByteBuf采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。 如果使用传统的JVM堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才能写入Socket 中。JVM堆内存的数据是不能直接写入Socket中的。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,某些情况下这部分内存也 会被频繁地使用,而且也可能导致OutOfMemoryError异常出现。Java里用DirectByteBuffer可以分配一块直接内存(堆外内存),元空间 对应的内存也叫作直接内存,它们对应的都是机器的物理内存。
我们可以用nio包提供的api来分配内存
//分配堆内存
ByteBuffer buffer = ByteBuffer.allocate(1000);
//分配直接内存
ByteBuffer buffer = ByteBuffer.allocateDirect(1000);
直接内存分配源码分析:
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
//判断是否有足够的直接内存空间分配,可通过-XX:MaxDirectMemorySize=<size>参数指定直接内存最大可分配空间,如果不指定默认为最大堆内存大小,
//在分配直接内存时如果发现空间不够会显示调用System.gc()触发一次full gc回收掉一部分无用的直接内存的引用对象,同时直接内存也 被释放掉
//如果释放完分配空间还是不够会抛出异常java.lang.OutOfMemoryError
Bits.reserveMemory(size, cap);
long base = 0;
try {
// 关键代码调用 unsafe 本地方法分配直接内存
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
// 分配失败,释放内存
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
// 使用Cleaner机制注册内存回收处理函数,当直接内存引用对象被GC清理掉时,
// 会提前调用这里注册的释放直接内存的Deallocator线程对象的run方法
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
// jdk源码
UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size))
UnsafeWrapper("Unsafe_AllocateMemory");
size_t sz = (size_t)size;
if (sz != (julong)size || size < 0) {
THROW_0(vmSymbols::java_lang_IllegalArgumentException());
}
if (sz == 0) {
return 0;
}
sz = round_to(sz, HeapWordSize);
// 调用os::malloc申请内存,内部使用malloc这个C标准库的函数申请内存
void* x = os::malloc(sz, mtInternal);
if (x == NULL) {
THROW_0(vmSymbols::java_lang_OutOfMemoryError());
}
//Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize);
return addr_to_java(x);
UNSAFE_END
使用直接内存的优缺点:
优点:
不占用堆内存空间,减少了发生GC的可能
java虚拟机实现上,本地IO会直接操作直接内存(直接内存=>系统调用=>硬盘/网卡),而非直接内存则需要二次拷贝(堆内 存=>直接内存=>系统调用=>硬盘/网卡)
缺点:
初始分配较慢
没有JVM直接帮助管理内存,容易发生内存溢出。为了避免一直没有FULL GC,最终导致直接内存把物理内存耗完。我们可以 指定直接内存的最大值,通过-XX:MaxDirectMemorySize来指定,当达到阈值的时候,调用system.gc来进行一次FULL GC,间 接把那些没有被使用的直接内存回收掉。
netty中分配读取buf
可以看到这里用的是直接内存,再跟进去
由于直接内存存在分配慢的缺点,这里还做了个性能优化,搞了个buf池,netty确实很值得一学。
最终分配出来的buf中有个指针指向直接内存地址
Netty 如何解决 selector 空轮循问题
关键方法io.netty.channel.nio.NioEventLoop#select
select默认等待1秒钟,在没有事件发生的情况下正常1秒会跳出一次循环,去看看有没有其他任务可以执行,它引入了一个计数器selectCnt,当1秒内计数达到默认512次时(可通过-Dio.netty.selectorAutoRebuildThreshold配置),认为大概率触发了selector的空轮循bug,会重建一个新的selector
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
// If a task was submitted when wakenUp value was true, the task didn't get a chance to call
// Selector#wakeup. So we need to check task queue again before executing select operation.
// If we don't, the task might be pended until select operation was timed out.
// It might be pended until idle timeout if IdleStateHandler existed in pipeline.
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
// - Selected something,
// - waken up by user, or
// - the task queue has a pending task.
// - a scheduled task is ready for processing
break;
}
if (Thread.interrupted()) {
// Thread was interrupted so reset selected keys and break so we not run into a busy loop.
// As this is most likely a bug in the handler of the user or it's client library we will
// also log it.
//
// See https://github.com/netty/netty/issues/2426
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely because " +
"Thread.currentThread().interrupt() was called. Use " +
"NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
}
selectCnt = 1;
break;
}
long time = System.nanoTime();
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
// timeoutMillis elapsed without anything selected.
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
// The code exists in an extra method to ensure the method is not too big to inline as this
// branch is not very likely to get hit very frequently.
// 关键代码
selector = selectRebuildSelector(selectCnt);
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
selectCnt - 1, selector);
}
}
} catch (CancelledKeyException e) {
if (logger.isDebugEnabled()) {
logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
selector, e);
}
// Harmless exception - log anyway
}
}
private Selector selectRebuildSelector(int selectCnt) throws IOException {
// The selector returned prematurely many times in a row.
// Rebuild the selector to work around the problem.
logger.warn(
"Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
selectCnt, selector);
rebuildSelector();
Selector selector = this.selector;
// Select again to populate selectedKeys.
selector.selectNow();
return selector;
}
around the infamous epoll 100% CPU bug.
/**
* Replaces the current {@link Selector} of this event loop with newly created {@link Selector}s to work
* around the infamous epoll 100% CPU bug.
*/
public void rebuildSelector() {
if (!inEventLoop()) {
execute(new Runnable() {
@Override
public void run() {
rebuildSelector0();
}
});
return;
}
rebuildSelector0();
}