Netty源码解析-服务端启动流程详解

 2023-01-24
原文作者:蚂蚁背大象 原文地址:https://juejin.cn/post/7077559435962351647

之前讲了很多关于Netty的组件相关的知识以及Netty启动过程中的一些调用关系。下面通过一个官网的例子(稍微增加了修改)来说明整个启动过程的每一步到底做了什么。

1. 官网示例

下述例子是在官网的例子上做了一些修改,增加了NioServerSocketChannel设置ChannelHandler。也就是标号5的位置。

    public class DiscardServer {
    
        private int port;
    
        public DiscardServer(int port) {
            this.port = port;
        }
    
        public void run() throws Exception {
            EventLoopGroup bossGroup = new NioEventLoopGroup(1); // (1)
            EventLoopGroup workerGroup = new NioEventLoopGroup();// (2)
            try {
                ServerBootstrap b = new ServerBootstrap(); // (3)
                b.group(bossGroup, workerGroup) // (3)
                    .channel(NioServerSocketChannel.class) // (4)
                    .handler(new ChannelInitializer<ServerSocketChannel>() {
                        @Override
                        protected void initChannel(ServerSocketChannel ch) {
                            ch.pipeline().addLast(new TimeServerBossOutHandler());
                            ch.pipeline().addLast(new TimeServerBossInHandler());
                        }
                    })// (5)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new TimeServerOutHandler());
                            ch.pipeline().addLast(new TimeServerInHandler());
                        }
                    })// (6)
                    .option(ChannelOption.SO_BACKLOG, 128)          // (7)
                    .childOption(ChannelOption.SO_KEEPALIVE, true); // (8)
    
                // Bind and start to accept incoming connections.
                ChannelFuture f = b.bind(port).sync(); // (9)
    
                // Wait until the server socket is closed.
                // In this example, this does not happen, but you can do that to gracefully
                // shut down your server.
                f.channel().closeFuture().sync();
            } finally {
                workerGroup.shutdownGracefully();
                bossGroup.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) throws Exception {
            int port = 8080;
            if (args.length > 0) {
                port = Integer.parseInt(args[0]);
            }
    
            new DiscardServer(port).run();
        }
    }

上述代码地址:github.com/mxsm/spring…

2.服务端启动流程详解分析

上述代码被分成了9个步骤,基本上涵盖了Netty的服务端基本开发。我们就一个个步骤分析,分析过程中两个将近的步骤会放在一起进行分析

2.1 EventLoopGroup的创建

步骤(1)、(2)创建**EventLoopGroup ,上述代码创建的是 NioEventLoopGroup**。那么做了一些什么事情呢?

Tips: EventLoopGroup还有 DefaultEventLoopGroup 、EpollEventLoopGroup(这个只能运行在Linux上面)

  • 创建EventExecutor,也就是NioEventLoop。
  • 创建Java NIO 的Selector, 这个是创建NioEventLoop的时候创建。

2.2 创建服务端的启动类

步骤(3)就是创建服务端的启动类,这个没什么好介绍的。创建完成后将bossGroup和workGroup分别设置到服务端启动服务类中。作用:

  • bossGroup负责监听来自客户端的连接,以及将连接转交给workGroup处理
  • workGroup主要负责客户端连接Channel的数据读写操作

2.3 设置创建的Channel的类型

对于服务端的设置Channel,其实有两类:ServerSocketChannel和SocketChannel,BossGroup创建的是ServerSocketChannel,而workGroup创建的是SocketChannel(这个会在下面讲到)。(4)这里设置的是boss的ChannelServerSocketChannel。

2.4 为ServerSocketChannel设置ChannelHandler

步骤(5)设置的ChannelServerSocketChannel的ChannelHandler,在不设置的情况下,其中有默认的添加 ChannelInitializer 在ServerBootstrap#init方法中:

    @Override
    void init(Channel channel) {
        setChannelOptions(channel, newOptionsArray(), logger);
        setAttributes(channel, newAttributesArray());
    
        ChannelPipeline p = channel.pipeline();
    
        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
    
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler); //(1)
                }
    
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

上面代码标号1的位置就是将步骤(5)设置的ChannelHandler添加到ChannelServerSocketChannel所绑定的ChannelPipeline中。

2.5 为SocketChannel设置ChannelHandler

步骤(6)为SocketChannel设置ChannelHandler,这个和步骤5相似但是注意下ChannelInitializer中的泛型类C是不一样的,步骤(5)是ServerSocketChannel,而步骤(6)是SocketChannel。

2.6 ServerSocketChannel和SocketChannel的设置

步骤(7)、(8)分别设置ServerSocketChannel和SocketChannel的TCP以及其他相关的参数设置。

Tips: 以上的步骤,主要是创建启动服务类以及设置ChannelHandler和Channel的配置。

2.7 绑定端口启动服务

步骤(9)主要是绑定端口,然后将之前步骤的设置串连起来。将服务启动。

    private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }
    
        if (regFuture.isDone()) {
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            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) {
                        promise.setFailure(cause);
                    } else {
                        promise.registered();
                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }
  • 创建NioServerSocketChannel的实例,在创建NioServerSocketChannel实例的同时也创建了当前NioServerSocketChannel的ID,内部接口Unsafe的实例对象,以及当前NioServerSocketChannel说绑定的ChannelPipeline。

  • 对NioServerSocketChannel的实例进行初始化, 初始化做了哪些工作?

    • NioServerSocketChannel设置之前设置的options配置
    • NioServerSocketChannel属性
    • 给NioServerSocketChannel的ChannelPipeline中添加ChannelInitializer,这里添加的ChannelInitializer会将之前案例代码中步骤(5)添加的ChannelHandler添加到NioServerSocketChannel的ChannelInitializer中,同时还添加了ServerBootstrapAcceptor(这个类的作用后面会讲到)
  • 将NioServerSocketChannel的实例注册到BossGroup的EventLoop上面,对于Nio来说就是注册到NioEventLoop上面。

  • 绑定启动服务器的本地端口,等待客户端连接

到这里就完成整个服务的启动工作。

3. 总结

netty服务端的启动和Java NIO的启动和工作的流程一致,只是将接收连接和处理连接的读写分开了。也就是Netty的Reactor的主从模型(图片来源网上)

202212302149111221.png

我是蚂蚁背大象,文章对你有帮助点赞关注我,文章有不正确的地方请您斧正留言评论~谢谢