Netty Server 源码分析(一)

146 阅读4分钟

Netty Server 源码分析

1、EventLoopGroup

EventLoopGroup 最终继承ScheduledExecutorService,这个是 java 的线程池,所以他的本质就是一个线程池,可以让你从中获取到新的线程并负责线程生命周期的管理。

1.1、NioEventLoopGroup

NioEventLoopGroup 在初始化时会默认传递一个0进入方法,这个0代表的最初的线程数量,并会设置一个空的 Executor 和 SelectorProvider.provider() 这个会获取到自己的 Selector。

//1 初始化
public NioEventLoopGroup() {
        this(0);
}
// 2 初始化
public NioEventLoopGroup(int nThreads) {
        this(nThreads, (Executor) null);
}

// 3 初始化 selector
public NioEventLoopGroup(int nThreads, Executor executor) {
        this(nThreads, executor, SelectorProvider.provider());
}

// 4 对默认值进行处理
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}

在后续中对默认值进行处理,线程数据如果为零的话,会从环境变量中获取 io.netty.eventLoopThreads 的值,如果获取不到话会用当前可用CPU核数*2来作为线程数量的值,当前这里有一个最小限制为1

private static final int DEFAULT_EVENT_LOOP_THREADS;

    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);
        }
    }

最终会走入下面方法,首先会对 executor 进行默认线程工厂的赋值,然后会有一个 EventExecutor 的数组,用来存放线程

/**
     * Create a new instance.
     *
     * @param nThreads          the number of threads that will be used by this instance.
     * @param executor          the Executor to use, or {@code null} if the default should be used.
     * @param args              arguments which will passed to each {@link #newChild(Executor, Object...)} call
     */
    protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
        if (nThreads <= 0) {
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        }

        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
				// 这一行是核心处理逻辑
        children = new EventExecutor[nThreads];
        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
                if (!success) {
                    for (int j = 0; j < i; j ++) {
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
                        EventExecutor e = children[j];
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }

        。。。 。。。
    }
@Override
   protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        return new NioEventLoop(this, executor, (SelectorProvider) args[0]);
   }

那么 NioLoopGroup 线程池管理的线程就是 NioEventLoop,每个 NioEventLoop 都会负责一部分客户端的 SockerChannel 因为在初始化的时候都会有自己的一个 Selector,所有这些 SocketChannel 都会注册到自己的 Selector 上去,这样每个 NioEventLoop 就会通过自己的 Selector 进行轮询处理客户端请求。

💡 整个 NioEventLoopGroup 就是一个线程池,线程数据为 CPU 核心数 * 2,最小为1。每个线程对象是 NioEventLoop ,每个线程有自己 SockerChannel 并将其注册到自己的 Selector上去进行轮询请求,将请求传给 Handler 进行请求处理。

2、ServerBootstrap

ServerBootstrap serverBootstrap =newServerBootstrap();
serverBootstrap
        .group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.SO_BACKLOG, 1024)
.childHandler(newChannelInitializer<SocketChannel>() {//创建通道初始化对象
//给 pipeline设置处置器
protected voidinitChannel(SocketChannel ch)throwsException {
//传入自定义的 handler
ch.pipeline().addLast(newNettyServerHandler());
            }
        });
//启动服务器并绑定端口
ChannelFuture cf = serverBootstrap.bind(6666).sync();

2.1 bind

  • ServerSocketChannel 对象的创建
// 用来初始化 ServerSocketChannel
final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
						// 创建 ServerSocketChannel 并监听 OP_ACCEPT 事件
            channel = channelFactory.newChannel();
						// 初始化我们设置的相关参数
						// 增加一个内置的 pipeline
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }

        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

核心是通过 channelFactory 来进行channel的创建,在我们构建 ServerBootStarp 的时候设置了 channel(NioServerSocketChannel.class) 属性,默认构建了 ReflectiveChannelFactory

// 设置参数时传递
public B channel(Class<? extends C> channelClass) {
        return channelFactory(new ReflectiveChannelFactory<C>(
                ObjectUtil.checkNotNull(channelClass, "channelClass")
        ));
    }

public ReflectiveChannelFactory(Class<? extends T> clazz) {
        ObjectUtil.checkNotNull(clazz, "clazz");
        try {
            this.constructor = clazz.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
                    " does not have a public non-arg constructor", e);
        }
    }

也就是说在进行 newChannel() 调用的时候实际是走的 ReflectiveChannelFactory ,对我们设置的 NioServerSocketChannel 类通过反射构建 ServerSocketChannel 实例并让其关注 OP_ACCEPT 事件。

public T newChannel() {
        try {
            return constructor.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
        }
    }
		// 获取默认的 Selector
		private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
		
    private static ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
						// 最终通过对 nio 底层的调用,获取 ServerSocketChannel 对象
            return provider.openServerSocketChannel();
        } catch (IOException e) {
            throw new ChannelException("Failed to open a server socket.", e);
        }
    }

    /**
     * Create a new instance
     */
    public NioServerSocketChannel() {
				// 这里通过反射用无参构造器进行对象的创建
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }
		
		// 同时还会绑定 OP_ACCEPT 事件监听连接请求
		public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

这个文档不好写,以上的话标题暂时不动了,后续的笔记不太适合大规模的代码的贴放,所以后续的笔记的话,纯采用文字+流程图的形式展示


3、连接事件

因为 NioEventLoop 是一个线程且继承自线程组不过只是有一个线程的线程组,所以他的核心逻辑是在 run() 方法中,除了处理调用 submit() 方法到队列中的任务外,就是调用自身 Selector#select() 方法进行事件的监听, 当获取到连接事件后,会在 doReadMessage() 中调用 nio 底层的 accept() 方法完成连接请求的处理并返回一个 socketChannel ,我们在启动 Netty Server 的时候设置了两个线程组 parent 和 child ,那么此时会将完成连接请求的 channel 注册到 childGroup 中的 Selector 上监听后续读写事件。

未命名文件 (2).png

4、读写事件

  • Read 事件

    当连接请求处理完毕后会将获取到的 SocketChannel 注册到 childGroup 中的某个线程的 Selector 上面,然后 childGroup 中的线程对自身的 Selector 进行轮询,当获取到 read 事件后,会将消息封装到 ByteBuf 中,所有我们自己写的 Handle 中都会对消息进行强转为 ByteBuf ,封装完毕后最终会根据 Piepline 找到我们自己实现的 Handle 去实现相对应的 read 事件的逻辑。

  • Write事件

    当我们收到 Read 事件 后会发送一个响应信息,会将响应消息放到一个 outBondMessage 缓冲中 最后会到我们自己实现的 Handle#channelReadComplete 调用 flush 方法,这个方法的话会调用底层 Nio 的write

未命名文件 (4).png