Netty(三十六) - Netty启动源码剖析

一、基本说明

1)、只有看过Netty源码,才能说是真正掌握了Netty框架。
2)、在 io.netty.example 包下,有很多Netty源码案例,可以用来分析。

二、启动过程源码剖析

2.1 目的

1)、用源码分析的方式走一下 Netty(服务器)的启动过程,更好的理解 Netty 的整体设计和运行机制。
2)、源码需要剖析到 Netty 调用 doBind 方法,追踪到 NioServerSocketChannel 的 doBind 方法
3)、要 Debug 程序到 NioEventLoop 类的run代码,无限循环,在服务器端运行

2.2 启动类分析

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)、在启动类 main 方法中,首先创建了关于 SSL 的配置类。
2)、在启动类 main 方法中,创建了两个 EventLoopGroup 对象:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();

  1. 这两个对象是整个 Netty 的核心对象,可以说,整个 Netty 的运作都依赖于它们。bossGroup 用于接收 Tcp 请求,它会将请求交给 workerGroup,workerGroup 会获取到真正的连接,然后和连接进行通信,比如读写编码解码等等。
  2. EventLoopGroup 是事件循环组(线程组)含有多个 EventLoop,可以注册 channel,用于在事件循环中去进行选择(和选择器相关)。
  3. new NioEventLoopGroup(1):这个 1 表示 bossGroup 事件组有 1 个线程,这个值可以指定,如果 new NioEventLoopGroup() 会含有默认个线程 cpu核数*2,即可以充分的利用多核的优势。
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
               "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
  1. 创建 EventExecutor 数组,children = new EventExecutor[nThreads]; 每个元素的类型就是 NIOEventLoop ,NIOEventLoop 实现了 EventLoop 接口 和 Executor 接口
  2. 在启动类 main 方法中,try 块中创建了一个 ServerBootstrap 对象,它是一个引导类,用于启动服务器和引导整个程序的初始化。它和 ServerChannel 关联,而 ServerChannel 继承了 Channel,所以它提供了一些方法供我们使用,比如:remoteAddress等。随后,变量 b(ServerBootstrap) 调用了 group 方法,将两个 group 放入到自己的字段中,用于后期引导使用。
  3. 然后添加了一个 channel,其中参数是一个 Class 对象,引导类将通过这个 Class 对象反射创建 ChannelFactory。然后添加了一些 TCP 的参数。【说明:Channel的创建在 bind 方法,Debug下可以找到:channel = channelFactory.newChannel();】
  4. 添加一个服务器专属的日志处理器
  5. 添加一个 SocketChannel (不是 ServerSocketChannel)的 handler
  6. 绑定端口并阻塞至连接成功
  7. 最后 main 线程阻塞等待关闭
  8. finally 块中的代码将在服务器关闭时优雅关闭所有资源

2.3 处理器源码

@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

1)、这是一个普通的处理器类,用于处理器客户端发送来的消息。

2.3 EventLoopGroup分析

1)、构造器方法,依次调用(CTRL+D)

public NioEventLoopGroup() {
	this(0);
}
public NioEventLoopGroup(int nThreads) {
	this(nThreads, (Executor) null);
}
public NioEventLoopGroup(int nThreads, Executor executor) {
	this(nThreads, executor, SelectorProvider.provider());
}
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider) {
	this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
                             final SelectStrategyFactory selectStrategyFactory) {
	super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
	super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}

2)、追踪到源码 抽象类 MultithreadEventExecutorGroup 的构造方法 MultithreadEventExecutorGroup 才是 NioEventLoopGroup 真正的构造方法,这里可以看成是一个模板方法,使用了设计模式的模板模式。
3)、MultithreadEventExecutorGroup

/**
* @param nThreads         使用的线程数,默认为 core*2 
* @param executor          执行器,如果传入null,则采用Netty默认的线程工厂和默认的执行器 ThreadPerTaskExecutor
* @param chooserFactory   单例 new DefaultEventExecutorChooserFactory()
* @param args              在创建执行器的时候传入固定参数
*/
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, 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 {
            	// 创建 NioEventLoop
                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) {
                            // 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);
        }

        Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
        // 将所有的单例线程池添加到一个 HashSet 中
        Collections.addAll(childrenSet, children);
        readonlyChildren = Collections.unmodifiableSet(childrenSet);
    }

1)、如果 executor 是 null,创建一个默认的 ThreadPerTaskExecutor,使用 Netty 默认的线程工厂
2)、根据传入的线程数(CPU*2),创建一个线程池(单例线程池)数组
3)、循环填充数组中的元素,如果异常,则关闭所有的单例线程池
4)、根据线程选择工厂创建一个 线程选择器
5)、为每一个单例线程池添加一个关闭监听器
6)、将所有的单例线程池添加到一个 HashSet 中

2.4 ServerBootstrap分析

1)、ServerBootstrap 是个空构造,但是有默认的成员变量

private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();
private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
private volatile EventLoopGroup childGroup;
private volatile ChannelHandler childHandler;

2)、基本使用

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

1)、链式调用:group 方法,将 bossGroup 和 workerGroup 传入,bossGroup 赋值给 parentGroup 属性,workerGroup 赋值给 childGroup 属性
2)、channel 方法传入 NioServerSocketChannel.class 对象。会根据这个 class 创建 channel 对象
3)、option 方法传入 TCP 参数,放在一个 LinkedHashMap 中
4)、handler 方法传入一个 handler ,这个 handler 只属于 ServerSocketChannel,而不是 SocketChannel
5)、childHandler 方法传入一个 handler ,这个 handler 将会在每个客户端连接的时候调用。供 SocketChannel 使用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yangxshn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值