Netty源码分析

本文深入分析了Netty的启动过程,包括EventLoopGroup的配置,ServerBootstrap的应用,以及绑定端口的细节。Netty通过NioEventLoopGroup管理线程,启动时初始化并注册channel,然后绑定端口,最终进入监听状态。同时概述了Netty接受请求时,EventLoop如何处理NIO事件并完成客户端连接的建立。
摘要由CSDN通过智能技术生成

从maven导入netty的源码包后,其中example包下的echo包下有一些源码示例,把这些源码拿到自己包下进行运行调试可以探究netty的运行奥秘

1.Netty启动过程

server端的源码如下:

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package org.zyxzyx.sources.echo;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;

/**
 * Echoes back any received data from a client.
 */
public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoServerHandler());
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

1.1EventLoopGroup分析

  • 启动类main方法中首先进行的是SSL的配置,这也反映了netty的确是支持SSL/TSL协议,更加的安全
  • 重点分析一下EventLoopGroup bossGroup和EventLoopGroup workerGroup,这两个对象是整个Netty 的核心对象,可以说,整个 Netty 的运作都依赖于他们。bossGroup 用于接受TCP 请求,他会将请求交给workerGroup,workerGroup 会获取到真正的连接,然后和连接进行通信,比如读写解码编码等操作

(1) new NioEventLoopGroup(1); 这个 1 表示 bossGroup 事件组有 1 个线程你可以指定,如果 new NioEventLoopGroup() 会含有默认个线程 cpu核数 * 2,即可以充分的利用多核的优势,源码最终会跳转到MultithreadEventLoopGroup类下的DEFAULT_EVENT_LOOP_THREADS变量,而DEFAULT_EVENT_LOOP_THREADS变量会在此类的静态代码块中初始化

    static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }

通过NettyRuntime.availableProcessors()方法即可获得当前CPU的核心数

(2) bossGroup和workerGroup均是NioEventLoopGroup类型的事件循环组(线程组),通过EventExecutor[ ] 数组去管理每个NioEventLoop用于在事件循环中注册channel或者选择selector
在这里插入图片描述
在源码中,bossGroup最终会执行到MultithreadEventExecutorGroup类下的 protected MultithreadEventExecutorGroup(int nThreads, Executor executor,EventExecutorChooserFactory chooserFactory, Object… args)方法,其中会执行children = new EventExecutor[nThreads];这一句代码,并且通过for循环去给每个元素赋值,真正的赋值语句是通过children[i] = newChild(executor, args);newChild方法在创建的同时会放入executor执行器
参数说明:

@param nThreads 使用的线程数,默认为 core * 2【可以追踪源码】
@param executor 执行器:如果传入 null, 则采用 Netty 默认的线程工厂和默认的执行器 ThreadPerTaskExecutor
@param chooserFactory 单例 new DefaultEventExecutorChooserFactory()
@param args args 在创建执行器的时候传入固定参数

        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];//相关的Executor也会被处理
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }
                chooser = chooserFactory.newChooser(children);

        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                if (terminatedChildren.incrementAndGet() == children.length) {
                    terminationFuture.setSuccess(null);
                }
            }
        };

        for (EventExecutor e: children) {
            e.terminationFuture().addListener(terminationListener);//并且会给当前子线程加监听器
        }

在这里插入图片描述
其中executor的真实类型是threadFactory
至于为什么EventExecutor数组可以放NioEventLoop类型的元素你可以看到NioEventLoop是EventExecutor接口的一个子实现类
在这里插入图片描述

1.2ServerBootstrap

在debug中bootstrap会包含许多信息
在这里插入图片描述
在源码中,bootstrap会调用ServerBootstrap类下的:

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup);  //用父类的group方法初始化BossGroup
        if (childGroup == null) {
            throw new NullPointerException("childGroup");
        }
        if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        }
        this.childGroup = childGroup; //再将WorkGroup赋值给自己的childGroup
        return this;
    }

并且会通过AbstractBootstrap类下的:

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            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();
            }
        }

为其添加了一个 channel,其中参数一个 Class 对象,引导类将通过这个 Class 对象反射创建 ChannelFactory。然后添加了一些配置:
(1)TCP 的参数。
(2)服务器专属的日志处理器 handler 。
(3) 添加一个 SocketChannel(不是 ServerSocketChannel)的 handler。
(4) 然后绑定端口并阻塞至连接成功。
(5) 最后main 线程阻塞等待关闭。
(6) finally 块中的代码将在服务器关闭时优雅关闭所有资源。
在这里插入图片描述
Server端的channel类型是NioServerSocketChannel
在这里插入图片描述
ChannelFactory的真实类型是ReflectiveChannelFactorty,Reflective的名称也从侧面反映出其是通过反射机制创建的

1.3绑定端口分析

服务端是通过ChannelFuture f = b.bind(PORT).sync();,主要通过bind( )方法来绑定端口,在debug中会进入到AbstractBootstrap类下的:

public ChannelFuture bind(SocketAddress localAddress) {
        validate();
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        }
        return doBind(localAddress);
    }

在进行判空操作后最终会进入doBind( )方法,doBind( )方法同样位于该类下源码如下:

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }

        if (regFuture.isDone()) {
            // At this point we know that the registration was complete and successful.
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            // Registration future is almost always fulfilled already, but just in case it's not.
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                        // IllegalStateException once we try to access the EventLoop of the Channel.
                        promise.setFailure(cause);
                    } else {
                        // Registration was successful, so set the correct executor to use.
                        // See https://github.com/netty/netty/issues/2586
                        promise.registered();

                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

其返回类型是ChannelFuture,这是一个异步执行的返回结果,其中有两个核心方法initAndRegister( ) 和 doBind0( )
initAndRegister源码如下:

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        channel = channelFactory.newChannel();

        /**
         * 说明:channelFactory.newChannel() 方法的作用通过 ServerBootstrap 的通道工厂反射创建一个 NioServerSocketChannel,具体追踪源码可以得到下面结论
         * (1)通过 NIO 的 SelectorProvider 的 openServerSocketChannel 方法得到 JDK 的 channel。目的是让 Netty 包装 JDK 的 channel。
         * (2)创建了一个唯一的 ChannelId,创建了一个 NioMessageUnsafe,用于操作消息,创建了一个 DefaultChannelPipeline 管道,是个双向链表结构,用于过滤所有的进出的消息。
         * (3)创建了一个 NioServerSocketChannelConfig 对象,用于对外展示一些配置。 

         * channel = channelFactory.newChannel();//NioServerSocketChannel

         * 说明:init 初始化这个 NioServerSocketChannel,具体追踪源码可以得到如下结论
         * (1) init 方法,这是个抽象方法 (AbstractBootstrap类的),由ServerBootstrap实现(可以追一下源码//setChannelOptions(channel,options,logger);)。
         * (2)设置 NioServerSocketChannel 的 TCP 属性。
         * (3)由于 LinkedHashMap 是非线程安全的,使用同步进行处理。
         * (4)对 NioServerSocketChannel 的 ChannelPipeline 添加 ChannelInitializer 处理器。
         * (5)可以看出,init 的方法的核心作用在和 ChannelPipeline 相关。
         * (6)从 NioServerSocketChannel 的初始化过程中,我们知道,pipeline 是一个双向链表,并且,他本身就初始化了 head 和 tail,这里调用了他的 addLast 方法,也就是将整个 handler 插入到 tail 的前面,因为 tail 永远会在后面,需要做一些系统的固定工作。
         */
        init(channel);
    } catch (Throwable t) {
        if (channel != null) {
            channel.unsafe().closeForcibly();
            return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        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;
}

说明:
基本说明:initAndRegister() 初始化 NioServerSocketChannel 通道并注册各个 handler,返回一个 future。
通过 ServerBootstrap 的通道工厂反射创建一个 NioServerSocketChannel。
init 初始化这个 NioServerSocketChannel。
config().group().register(channel) 通过 ServerBootstrap 的 bossGroup 注册 NioServerSocketChannel。
最后,返回这个异步执行的占位符即 regFuture。

其中init 方法会调用DefaultChannelPipeline下的addLast方法,现在进入到 addLast 方法内查看:

@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        checkMultiplicity(handler);
        newCtx = newContext(group, filterName(name, handler), handler);
        addLast0(newCtx);
        if (!registered) {
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }
        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
    }
    callHandlerAdded0(newCtx);
    return this;
}

说明:
(1) addLast 方法,在 DefaultChannelPipeline 类中
(2) addLast 方法这就是 pipeline 方法的核心,检查该 handler 是否符合标准。
(3) 创建一个 AbstractChannelHandlerContext 对象,这里说一下,ChannelHandlerContext 对象是 ChannelHandler 和 ChannelPipeline 之间的关联,每当有 ChannelHandler 添加到 Pipeline 中时,都会创建 Context。Context 的主要功能是管理他所关联的 Handler 和同一个 Pipeline 中的其他 Handler 之间的交互。
(4) 将 Context 添加到链表中。也就是追加到 tail 节点的前面。
(5) 最后,同步或者异步或者晚点异步的调用 callHandlerAdded0 方法

其次,另外一个doBind0 方法源码如下:

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()) {
                //bind方法这里下断点,这里下断点,来玩!!
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}

说明:
该方法的参数为 initAndRegister 的 future,NioServerSocketChannel,端口地址,NioServerSocketChannel 的 promise
一直 debug:

// 将调用 LoggingHandler 的 invokeBind 方法,最后会追到
// DefaultChannelPipeline 类的 bind
// 然后进入到 unsafe.bind 方法 debug,注意要追踪到
// unsafe.bind,要 debug 第二圈的时候,才能看到。

@Override
public void bind (ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
    unsafe.bind(localAddress,promise);
}

// 继续追踪 AbstractChannel 的 

public final void bind (final SocketAddress localAddress, final ChannelPromise promise) {
    //....
    try{
        //!!!! 小红旗可以看到,这里最终的方法就是 doBind 方法,执行成功后,执行通道的 fireChannelActive 方法,告诉所有的 handler,已经成功绑定。
        doBind(localAddress);//
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        closeIfClosed();
        return;
    }
}

最终 doBind 就会追踪到 NioServerSocketChannel 的 doBind,说明 Netty 底层使用的是 NIO

@Override
protected void doBind (SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        javaChannel().bind(localAddress, config.getBacklog());
    } else {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}

回到 bind 方法(alt + v),最后一步:safeSetSuccess(promise),告诉 promise 任务成功了。其可以执行监听器的方法了。到此整个启动过程已经结束了,ok 了

继续 atl + v 服务器就回进入到(NioEventLoop 类)一个循环代码,进行监听

@Override
protected void run() {
    for(;;) {
        try{

        }
    }
}

1.4Netty服务端启动过程梳理
(1) 创建 2 个 EventLoopGroup 线程池数组。数组默认大小 CPU * 2,方便 chooser 选择线程池时提高性能
(2) BootStrap 将 boss 设置为 group 属性,将 worker 设置为 childer 属性
(3) 通过 bind 方法启动,内部重要方法为 initAndRegister 和 dobind 方法
(4) initAndRegister 方法会反射创建 NioServerSocketChannel 及其相关的 NIO 的对象,pipeline,unsafe,同时也为 pipeline 初始了 head 节点和 tail 节点。
(5) 在 register0 方法成功以后调用在 dobind 方法中调用 doBind0 方法,该方法会调用 NioServerSocketChannel 的 doBind 方法对 JDK 的 channel 和端口进行绑定,完成 Netty 服务器的所有启动,并开始监听连接事件。

2.Netty接受请求过程:

1.从之前服务器启动的源码中,我们得知,服务器最终注册了一个 Accept 事件等待客户端的连接。我们也知道,NioServerSocketChannel 将自己注册到了 boss 单例线程池(reactor 线程)上,也就是 EventLoop。
2.先简单说下 EventLoop 的逻辑(后面我们详细讲解 EventLoop)
EventLoop 的作用是一个死循环,而这个循环中做 3 件事情:

1.有条件的等待 NIO 事件。
2.处理 NIO 事件。
3.处理消息队列中的任务。
4.仍用前面的项目来分析:进入到 NioEventLoop 源码中后,在 private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) 方法开始调试最终我们要分析到 AbstractNioChannel 的 doBeginRead 方法,当到这个方法时,针对于这个客户端的连接就完成了,接下来就可以监听读事件了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值