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

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

之前讲了很多关于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的主从模型(图片来源网上)

image.png

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

Guess you like

Origin juejin.im/post/7077559435962351647