手撕netty源码(一)- NioEventLoopGroup

文章目录

  • 前言
  • [一、NIO 与 netty](#一、NIO 与 netty)
  • [二、NioEventLoopGroup 对象的创建过程](#二、NioEventLoopGroup 对象的创建过程)
    • [2.1 创建流程图](#2.1 创建流程图)

前言

本文是手撕netty源码系列的开篇文章,会先介绍一下netty对NIO关键代码的封装位置,主要介绍 NioEventLoopGroup 对象的创建过程,看看new一个对象可以做哪些事情。


一、NIO 与 netty

平时使用NIO的主要步骤:

java 复制代码
/*创建选择器的实例*/
Selector selector = Selector.open();
/*创建ServerSocketChannel的实例*/
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

/*设置通道为非阻塞模式*/
serverSocketChannel.configureBlocking(false);
/*绑定端口*/
serverSocketChannel.socket().bind(new InetSocketAddress(port));
/*注册事件,表示关心客户端连接*/
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
while(true){
	/*获取当前有哪些事件*/
    selector.select(1000);
    /*获取事件的集合*/
    Set<SelectionKey> selectionKeys = selector.selectedKeys();
    Iterator<SelectionKey> iterator = selectionKeys.iterator();
    while(iterator.hasNext()){
         SelectionKey key = iterator.next();
         /*我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。
         如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活
         的键出现,这会导致我们尝试再次处理它。*/
         iterator.remove();
         handleInput(key);
    }
}

/*处理事件的发生*/
private void handleInput(SelectionKey key) throws IOException {
     if(key.isValid()){
         /*处理新接入的客户端的请求*/
         if(key.isAcceptable()){
             /*获取关心当前事件的Channel*/
             ServerSocketChannel ssc
                     = (ServerSocketChannel) key.channel();
             /*接受连接*/
             SocketChannel sc = ssc.accept();
             System.out.println("==========建立连接=========");
             sc.configureBlocking(false);
             /*关注读事件*/
             sc.register(selector,SelectionKey.OP_READ);
         }
         /*处理对端的发送的数据*/
         if(key.isReadable()){
             SocketChannel sc = (SocketChannel) key.channel();
             /*创建ByteBuffer,开辟一个缓冲区*/
             ByteBuffer buffer = ByteBuffer.allocate(1024);
             /*从通道里读取数据,然后写入buffer*/
             int readBytes = sc.read(buffer);
             if(readBytes>0){
                 /*将缓冲区当前的limit设置为position,position=0,
                 用于后续对缓冲区的读取操作*/
                 buffer.flip();
                 /*根据缓冲区可读字节数创建字节数组*/
                 byte[] bytes = new byte[buffer.remaining()];
                 /*将缓冲区可读字节数组复制到新建的数组中*/
                 buffer.get(bytes);
                 String message = new String(bytes,"UTF-8");
                 System.out.println("服务器收到消息:"+message);
                 /*处理数据*/
                 String result = Const.response(message);
                 、、、、、

             }else if(readBytes<0){
                 /*取消特定的注册关系*/
                 key.cancel();
                 /*关闭通道*/
                 sc.close();
             }
         }
         、、、、
     }
 }

平时使用netty的主要步骤:

java 复制代码
// 配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
	.channel(NioServerSocketChannel.class)
    .option(ChannelOption.SO_BACKLOG, 1024)
    .childHandler(new ServerInit());

// 绑定端口,同步等待成功
b.bind(NettyConstant.SERVER_PORT).sync();

那么,netty 对 NIO 的封装具体体现在哪里呢?先揭晓答案,后续一点点细嚼慢咽

  1. 创建选择器的实例
    io/netty/channel/nio/NioEventLoop.java
java 复制代码
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
             SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
             EventLoopTaskQueueFactory taskQueueFactory, EventLoopTaskQueueFactory tailTaskQueueFactory) {
    super(parent, executor, false, newTaskQueue(taskQueueFactory), newTaskQueue(tailTaskQueueFactory),
            rejectedExecutionHandler);
    this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
    this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
    final SelectorTuple selectorTuple = openSelector();
    this.selector = selectorTuple.selector;
    this.unwrappedSelector = selectorTuple.unwrappedSelector;
}
  1. 创建ServerSocketChannel的实例
    io/netty/channel/socket/nio/NioServerSocketChannel.java
java 复制代码
private static ServerSocketChannel newChannel(SelectorProvider provider, InternetProtocolFamily family) {
    try {
        ServerSocketChannel channel =
                SelectorProviderUtil.newChannel(OPEN_SERVER_SOCKET_CHANNEL_WITH_FAMILY, provider, family);
        return channel == null ? provider.openServerSocketChannel() : channel;
    } catch (IOException e) {
        throw new ChannelException("Failed to open a socket.", e);
    }
}
  1. 设置通道为非阻塞模式
    io/netty/channel/nio/AbstractNioChannel.java
java 复制代码
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                logger.warn(
                            "Failed to close a partially initialized socket.", e2);
            }

            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }
  1. 绑定端口
    io/netty/bootstrap/AbstractBootstrap.java
java 复制代码
private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {

        // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
        // the pipeline in its channelRegistered() implementation.
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (regFuture.isSuccess()) {
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }
  1. 注册事件,表示关心客户端连接
    io/netty/channel/nio/AbstractNioChannel.java
java 复制代码
protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }
  1. 获取当前事件的集合
  2. 处理事件
    io/netty/channel/nio/NioEventLoop.java
java 复制代码
private int select(long deadlineNanos) throws IOException {
   if (deadlineNanos == NONE) {
        return selector.select();
    }
    // Timeout will only be 0 if deadline is within 5 microsecs
    long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
    return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
}

private void processSelectedKeys() {
    if (selectedKeys != null) {
        processSelectedKeysOptimized();
    } else {
        processSelectedKeysPlain(selector.selectedKeys());
    }
}

其实,netty 也不难对吧
学习netty,主要学习它的设计思想和对性能优化的巧妙处理,当工作需要时,能够灵活运用

二、NioEventLoopGroup 对象的创建过程

2.1 创建流程图

可以看到,其实我们传的线程数量实际控制的是NioEventLoop对象创建的数量,而每个 NioEventLoop 其实是一个Executor执行器,那么至此,我们只是相当于创建了两个 NioEventLoopGroup 对象,他们分别有自己的children执行器 NioEventLoop 数组,同一个数组内的 NioEventLoop 共享一个ThreadPerTaskExecutor执行器,但是现在这个执行器后续如何处理事件和如何调度还不知道,后续会讲到,本文先看看创建NioEventLoopGroup对象都做了什么

相关推荐
jonyleek10 分钟前
数据可视化:JVS-BI仪表盘图表样式配置全攻略,打造个性化数据展示!
java·大数据·信息可视化·数据挖掘·数据分析·自动化·软件需求
WangMing_X10 分钟前
C# 单个函数实现各进制数间转换
java·开发语言·算法·c#·winform·软件
天人合一peng17 分钟前
20201010 MTAP-3DGAM审稿意见
后端·3d·restful
南宫生24 分钟前
贪心算法理论基础和习题【算法学习day.17】
java·学习·算法·leetcode·链表·贪心算法
jc0803kevin29 分钟前
solidity的struct对象,web3j java解析输出参数
java·web3·solidity
勇敢滴勇33 分钟前
【C++】继承和多态常见的面试问题
java·c++·面试
nice6666037 分钟前
初识JDBC
java·数据库·sql·mysql·idea
计算机学姐39 分钟前
基于SpringBoot的汽车票网上预订系统
java·vue.js·spring boot·后端·mysql·java-ee·mybatis
screamn44 分钟前
Sentinel详解
java·sentinel
哎呦没1 小时前
农村扶贫管理:SpringBoot解决方案
java·spring boot·后端