我对 netty 服务端启动的理解和源码深入剖析

本文适合有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的线程模型

在这里插入图片描述

该模型设计要点

  1. Netty 抽象出两组线程池BossGroup和WorkerGroup,BossGroup专门负责接收客户端的连接, WorkerGroup专 门负责网络的读写
  2. BossGroup和WorkerGroup类型都是NioEventLoopGroup
  3. NioEventLoopGroup 相当于一个事件循环线程组, 这个组中含有多个事件循环线程 , 每一个事件循环线程是 NioEventLoop
  4. 每个NioEventLoop都有一个selector (就是nio的selector), 用于监听注册在其上的socketChannel(nio的socketChannel)的网络通讯
  5. 每个Boss NioEventLoop线程内部循环执行的步骤有 3 步
    处理accept事件 , 与client 建立连接 , 生成 NioSocketChannel
    将NioSocketChannel注册到某个worker NIOEventLoop上的selector
    处理任务队列的任务 , 即runAllTasks
  6. 每个worker NIOEventLoop线程循环执行的步骤
    轮询注册到自己selector上的所有NioSocketChannel 的read, write事件
    处理 I/O 事件, 即read , write 事件, 在对应NioSocketChannel 处理业务
    runAllTasks处理任务队列TaskQueue的任务 ,一些耗时的业务处理一般可以放入TaskQueue中慢慢处
    理,这样不影响数据在 pipeline 中的流动处理
  7. 每个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端可能会收到如下几种情况的数据。
在这里插入图片描述

解决方案

  1. 消息定长度,传输的数据大小固定长度,例如每段的长度固定为100字节,如果不够空位补空格
  2. 在数据包尾部添加特殊分隔符,比如下划线,中划线等,这种方法简单易行,但选择分隔符的时候一定要注意每条数据的内部一定不 能出现分隔符。
  3. 发送长度:发送每条数据的时候,将数据的长度一并发送,比如可以选择每条数据的前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();
    }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
精通并发与 netty 视频教程(2018)视频教程。 精通并发与netty视频教程(2018)视频教程 netty视频教程 Java视频教程目录: 1_学习的要义 2_Netty宏观理解 3_Netty课程大纲深度解读 4_项目环境搭建与Gradle配置 5_Netty执行流程分析与重要组件介绍 6_Netty回调与Channel执行流程分析 7_NettySocket编程详解 8_Netty多客户端连接与通信 9_Netty读写检测机制与长连接要素 10_Netty对WebSocket的支援 11_Netty实现服务器端与客户端的长连接通信 12_Google Protobuf详解 13_定义Protobuf文件及消息详解 14_Protobuf完整实例详解 15_Protobuf集成Netty与多协议消息传 递 16_Protobuf多协议消息支援与工程最佳实践 17_Protobuf使用最佳实践与Apache Thrift介绍 18_Apache Thrift应用详解与实例剖析 19_Apache Thrift原理与架构解析 20_通过Apache Thrift实现Java与Python的RPC调用 21_gRPC深入详解 22_gRPC实践 23_Gradle Wrapper在Gradle项目构建中的最佳实践 24_gRPC整合Gradle与代码生成 25_gRPC通信示例与JVM回调钩子 26_gRPC服务器流式调用实现 27_gRPC双向流式数据通信详解 28_gRPC与Gradle流畅整合及问题解决的完整过程与思考 29_Gradle插件问题解决方案与Nodejs环境搭建 30_通过gRPC实现Java与Nodejs异构平台的RPC调用 31_gRPC在Nodejs领域中的静态代码生成及与Java之间的RPC通信 32_IO体系架构系统回顾与装饰模式的具体应用 33_Java NIO深入详解与体系分析 34_Buffer中各重要状态属性的含义与关系图解 35_Java NIO核心类源码解读与分析 36_文件通道用法详解 37_Buffer深入详解 38_NIO堆外内存与零拷贝深入讲解 39_NIO中Scattering与Gathering深度解析 40_Selector源码深入分析 41_NIO网络访问模式分析 42_NIO网络编程实例剖析 43_NIO网络编程深度解析 44_NIO网络客户端编写详解 45_深入探索Java字符集编解码 46_字符集编解码全方位解析 47_Netty服务器与客户端编码模式回顾及源码分析准备 48_Netty与NIO系统总结及NIO与Netty之间的关联关系分析 49_零拷贝深入剖析及用户空间与内核空间切换方式 50_零拷贝实例深度剖析 51_NIO零拷贝彻底分析与Gather操作在零拷贝中的作用详解 52_NioEventLoopGroup源码分析与线程数设定 53_Netty对Executor的实现机制源码分析 54_Netty服务端初始化过程与反射在其中的应用分析 55_Netty提供的Future与ChannelFuture优势分析与源码讲解 56_Netty服务器地址绑定底层源码分析 57_Reactor模式透彻理解及其在Netty中的应用 58_Reactor模式与Netty之间的关系详解 59_Acceptor与Dispatcher角色分析 60_Netty的自适应缓冲区分配策略与堆外内存创建方式 61_Reactor模式5大角色彻底分析 62_Reactor模式组件调用关系全景分析 63_Reactor模式与Netty组件对比及Acceptor组件的作用分析 64_Channel与ChannelPipeline关联关系及模式运用 65_ChannelPipeline创建时机与高级拦截过滤器模式的运用 66_Netty常量池实现及ChannelOption与Attribute作用分析 67_Channel与ChannelHandler及ChannelHandlerContext之间的关系分析 68_Netty核心四大组件关系与构建方式深度解读 69_Netty初始化流程总结及Channel与ChannelHandlerContext作用域分析 70_Channel注册流程深度解读 71_Channel选择器工厂与轮询算法及注册底层实现 72_Netty线程模型深度解读与架构设计原则 73_Netty底层架构系统总结与应用实践 74_Netty对于异步读写操作的架构思想与观察者模式的重要应用 75_适配器模式与模板方法模式在入站处理器中的应用 76_Netty项目开发过程中常见且重要事项分析 77_Java NIO Buffer总结回顾与难点拓展 78_Netty

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值