Netty之旅三:Netty服务端启动源码分析,一梭子带走!

Netty服务端启动流程源码分析

前记

哈喽,自从上篇《Netty之旅二:口口相传的高性能Netty到底是什么?》后,迟迟两周才开启今天的Netty源码系列。将由我的好朋友小飞分享 《Netty服务端启动流程源码分析》源码分析的第一篇文章,下一篇我会分享客户端的启动过程源码分析。通过源码的阅读,我们将会知道,Netty 服务端启动的调用链是非常长的,同时肯定也会发现一些新的问题,随着我们源码阅读的不断深入,相信这些问题我们也会一一攻破。

废话不多说,直接上号!

一、从EchoServer示例入手

netty-example:EchoServer.png

示例从哪里来?任何开源框架都会有自己的示例代码,Netty源码也不例外,如模块netty-example中就包括了最常见的EchoServer示例,下面通过这个示例进入服务端启动流程篇章。

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

        // 1. 声明Main-Sub Reactor模式线程池:EventLoopGroup
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        // 创建 EchoServerHandler 对象
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
   
            // 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(serverHandler);
                 }
             });

            // 3. 绑定端口即启动服务端,并同步等待
            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // 4. 监听服务端关闭,并阻塞等待
            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
   
            // 5. 优雅地关闭两个EventLoopGroup线程池 
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  1. [代码行18、19]声明Main-Sub Reactor模式线程池:EventLoopGroup

创建两个EventLoopGroup 对象。其中,bossGroup用于服务端接受客户端的连接,workerGroup用于进行客户端的 SocketChannel的数据读写。

(关于EventLoopGroup不是本文重点所以在后续文章中进行分析)

  1. [代码行23-39]声明服务端启动引导器,并设置相关属性

AbstractBootstrap是一个帮助类,通过方法链(method chaining)的方式,提供了一个简单易用的方式来配置启动一个Channelio.netty.bootstrap.ServerBootstrap ,实现AbstractBootstrap 抽象类,用于Server 的启动器实现类。io.netty.bootstrap.Bootstrap ,实现 AbstractBootstrap 抽象类,用于 Client 的启动器实现类。如下类图所示:

![AbstractBootstrap类继承.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d877706356d4427a9afd4b13d7177142~tplv-k3u1fbpfcp-zoom-1.image)

(EchoServer示例代码中,我们看到 ServerBootstrapgroupchanneloptionchildHandler 等属性链式设置都放到关于AbstractBootstrap体系代码中详细介绍。)

  1. [代码行43]绑定端口即启动服务端,并同步等待

先调用 #bind(int port) 方法,绑定端口,后调用 ChannelFuture#sync() 方法,阻塞等待成功。对于bind操作就是本文要详细介绍的"服务端启动流程"。

  1. [代码行47]监听服务端关闭,并阻塞等待

先调用 #closeFuture() 方法,监听服务器关闭,后调用 ChannelFuture#sync() 方法,阻塞等待成功。 注意,此处不是关闭服务器,而是channel的监听关闭。

  1. [代码行51、52]优雅地关闭两个EventLoopGroup线程池

finally代码块中执行说明服务端将最终关闭,所以调用 EventLoopGroup#shutdownGracefully() 方法,分别关闭两个EventLoopGroup对象,终止所有线程。

二、服务启动过程

在服务启动过程的源码分析之前,这里回顾一下我们在通过JDK NIO编程在服务端启动初始的代码:

 serverSocketChannel = ServerSocketChannel.open();
 serverSocketChannel.configureBlocking(false);
 serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
 selector = Selector.open();
 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

这5行代码标示一个最为熟悉的过程:

  • 打开serverSocketChannel
  • 配置非阻塞模式
  • channelsocket绑定监听端口
  • 创建Selector
  • serverSocketChannel注册到 selector

后面等分析完Netty的启动过程后,会对这些步骤有一个新的认识。在EchoServer示例中,进入 #bind(int port) 方法,AbstractBootstrap#bind()其实有多个方法,方便不同地址参数的传递,实际调用的方法是AbstractBootstrap#doBind(final SocketAddress localAddress) 方法,代码如下:

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 {
   
  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值