Netty服务端启动源码解析

本文详细解析了Netty服务端启动的整个流程,包括Channel的创建与初始化、注册Selector及端口绑定等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是Netty

  •  异步事件驱动框架,用于快速开发高性能服务端和客户端
  •  封装了JDK底层BIONIO模型,提供高度可用的API
  •  自带编解码器解决拆包粘包问题,用户只用关心业务逻辑
  •  精心设计的reactor线程模型支持高并发海量连接
  •  自带各种协议栈让你处理任何一种通用协议都几乎不用亲自动手

Netty的抽象流程

   首先,我们给出一张简单的socket运行流程,并用netty进行抽象描述,可如下图表示:

   图中的每一个步骤,下面都对应的netty的一个组件,此处就不多做描述了,各位自行理解。

Demo代码

public final class Server {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
                    .handler(new ServerHandler())
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new AuthHandler());
                            //..

                        }
                    });

            ChannelFuture f = b.bind(8888).sync();

            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

  上面即是我本次用到的demo,采用的maven管理jar包,netty版本用到的是4.1.6.Final。

Netty服务端启动源码分析

1. 创建服务端Channel

  首先调用bind()方法设置绑定端口,即用户代码的入口,bind()方法中调用initAndRegister()方法初始化并注册,而在initAndRegister()方法中,则使用了newChannel()方法创建服务端channel。在netty中创建服务端channel的方法,采用到了反射的机制。我们追踪源码跟进去就可以看到,如下:

       bind方法中,returndoBind方法,doBand方法中调用initAndRegister()方法,点进去看initAndRegister()内,可以看到如下:

  在initAndRegister()方法中,又调用了newChannel()方法,创建完服务端的channel后,再调用init(channel)用来初始化,我们先追踪newChannel方法,如下图:


  从newChannel()方法,我们可以知道,clazz.newInstance()是通过反射机制来创建channel,而我们的DEMO中,clazz类是传过来的,如图:

  从上图,可知道我们将NioServerSocketChannel.class传递给了channel方法处理,点进去channel方法,我们可以看到如下:

  该方法,会将channelClass封装成一个ReflectiveChannelFactory,即把channelClass这个类传递给RefectiveChannelFactory构造方法,通过反射的方式来创建channel,而这个channelClass就是上面讲到的NioServerSocketChannel.class,我们先进去ReflectiveChannelFactory的构造方法,如下:

  可以看出该方法实际上就是返回了传过来的NioServerSocketChannel,即调用了其无参构造函数。而至于在这个构造函数将干什么事,我们可以追踪下NioServerSocketChannel这个类。如下所示:

  在这个无参构造函数,通过调用newSocket()方法,来创建底层的JDK底层的channel,点进去可以看到该方法,主要是通过SelectorProvider去调用JDK底层的openServerSocketChannel方法,从而去创建服务端的ServerSocketChannel,而这个ServerSocketChannel实际上就是JDK底层的channel

  上述基本就将服务端的channel创建完毕了,接下了查看其TCP参数的配置类,在NioServerSocketChannel下,同样可以看到下图代码:

  该配置类也主要是通过构造函数来完成,将JDK创建完成的channel传进去,这个Config就是为了后续对TCP底层参数获取的时候,可以通过这个Config配置,config可以是serverSocket配置的一个抽象。

  在config配置上方,我们可以看到该构造函数调用了父类的构造函数,点击进去,接下来分下下父类的构造函数,我们对父类构造函数进行追踪:


  此处传进来的channel,是jdk底层创建完成的channelch.configureBlocking(false)这句话表明设置服务端的channel是一个非阻塞的过程。另外要注意的是,不过服务端的channel还是客户端的channel都继承一个AbstractChannel,另外点进去查看super(parent),如下所示:

  该构造函数主要是创建了三个属性,一个id就是channel的唯一标识,unsafe就是对应channel底层tcp读写相关操作的一个类(不建议直接拿来用),pipeline是服务端和客户端逻辑相关的一个逻辑链。

  最后给出一个大纲,来粗略了解下NioServerSocketChannel类的构造函数主要做哪些事情。如下图所示:

  首先通过newSocket()方法创建JDL底层的channel,接下来创建跟channel相关的config类,这个config类主要是跟channel相关的tcp参数的一些配置,然后,调用configureBlocking(false)这个方法,将服务端channel设置成非阻塞模式,最后在channel上创建pipelin到此,创建Channel的部分就讲解完毕。

2. 初始化服务端Channel

  初始化的大体方法调用,如下:

  从上面这个大纲图,一一进行源码追踪。我们从上一小节中,initAndRegister()方法中的init()方法用来初始化channel,现在我们来追踪下init的源码,点击进入serverBootStrap的实现类:


  这方法中,主要包含两个过程,一个获取用户自定义配置的Options,然后绑定到channelconfig配置中,一个获取用户自定义的属性,然后绑定channel的属性中。然后看下面的:

  上标记的操作其实都是一些简单操作,主要也是将用户自定义的选项和属性,用两个局部变量进行保存。属性配置完成之后,拿到服务端的piplinepipline是在创建serverSocketChannel的时候创建的,把上面保存的两个属性传进去给pipline,代码如下:


      pipeline还需要将用户自定义的handler处理链添加进去,配置进去后,在接下来的代码中,我们可以看到netty将自动添加连接器ServerBootstrapAcceptor,也相当于自带的handler,其中后面两个属性,是之前所定义的,而currentChildHandlernetty使用最外层链式调用的childHandler()方法,也是用户自定义的,而currentChildGroup,也是由最外层用户自定义确定的,如下图:

  这图上的代码,也是我用来分析NettyDemo。总的来说,初始化channel,保存用户自定义的属性,根据这zdn属性创建一个连接接入器,这个链接接入器每次accept上新的连接之后,都会使用这些属性对新的连接进行配置。

3. 注册selector

  服务端的channel创建初始化完成之后,需要将channel注册到事件轮询器seletor上面去,回到initAndRegister()方法,查看到下面这条语句:

  这个方法最终会调用到AbstractChannelregister方法,这个方法也是我们的入口:

        AbstractChannel.this.eventLoop = eventLoop这句简单的赋值操作,告诉channel后续所有的IO事件相关的操作,都是交给eventLoop来处理, register0(promise)就是做一个实际的注册,进入register0,会发现它实际就只做了三件事情:


  三件事就如同上述标注的一样,关注点主要在register方法上,点进去看abstractNioChannel方法,如下:

  从中,我们可以看到netty的注册最终调用的还是jdk底层的register方法,如上面讲述到的,javachannel在创建服务端channel的时候,会创建jdk底层相关的channel,然后对这个channel保存到成员变量里面,保存的代码如下:

  然后,我们接着看javachannelregister方法,该方法三个参数,第一个是需要注册到的selector,第二个opts是我们需要关心的事件,此处,不需要关注,输入的0,代表不关心任何事件,只是单独地把channel绑定到selector上面去,最后this属性是就意味着是服务端的channel,当selector轮询到java上面的读写事件,可以直接把这个this,也就是这个绑定的channel,针对这个nettyniochannel做一个事件的传播。

  再回头看后面两个事情,其实,可以对应我们demo入口的handler

  进入ServerHandler(),如下:

  运行下代码,会看到下面这输出:


  这输出,基本上就是对应pipeline的两个过程,但要注意的是channelActive并不是对应pipelinefireChannelActive方法,因为netty在这个过程只是做一个selector绑定操作,并没有绑定端口,所以isActive返回的是false

  最后,总结下绑定的过程:

  先通过AbstractChannel.register(channel)作为一个入口,register方法中,将调用到eventLoop进行线程的绑定,同时调用register0方法进行实际注册,register0方法中,又实践上实现doRegister方法调用jdk底层进行注册,将当前channel绑定到selector上面去,同时调用invokeHandlerAddedIfNeeded方法和fireChannelRegistered方法进行事件传播,最终传播到我们的用户方法。

4. 端口绑定

  先来张总结图大纲:

  调用doBind()方法,将端口实际绑定到本地,doBind方法将会调用的jdkjavaChannel.doBind()方法,通过jdk底层的channel进行端口的绑定,端口绑定成功后,调用pipeline.fireChannelActive(),传播一个active事件,在传播的时候,会从HeadContext开始触发,HeadContext把这个事件向前传播的时候,最终会调用到HeadContext.readIfIsAutoRead()方法,而这个方法将会触发read事件,而这个read事件通过层层调用,最终会调用到服务端channelbeginRead方法,而这个beginRead方法完成向selector注册一个accept事件。

     HeadContext.readIfIsAutoRead()方法将一个之前注册到selector上的事件,重新绑定成accept事件,有新连接进来,selector就会轮询到accept事件,最终就会将这个链接交给netty处理。

  进入NioServerSocketChannel类中的doBind方法,如下:

  这个方法主要负责两个事件,一个是doBind()方法,一个是调用pipeline方法fireChannelActive底层的事件,其中doBind()方法中代码如下:

  要注意的是,当端口绑定之前wasActive的值为false,绑定完成后,isActive()返回为true,此时需要将active事件进行传播,会调用到下面这方法:

  即先从HandlerContext方法先调用,传播过程中,会传播到:

  点进去看doBeginRead方法:

  首先拿到selectionKey,该值就是之前注册服务端channelselector上去的返回值,接下来拿到selectionKey的感兴趣事件,注册服务端channel上的,实际上是绑定一个0seletor上去,所以获取到的IntersetOp0,判断即为获取到的变量是否和获取到的accept事件判断。前面注册一个事件,在这个事件上再增加一个事件。也可理解为,服务端端口绑定成功后,需要告诉seletor,我需要关心一个accept事件,selector如果轮询到一个Accept事件的时候,就会把这个事件交给netty处理。

  当你端口绑定完成后,会触发一个active事件,这个active事件最终会调用到channel的一个read事件,read对服务端channel来说,就是我可以读一个新的连接了。

  以上就是对netty服务端demo启动源码的整体分析,可能有很多地方没解释周到,大家可以跟进源码进行追踪,共同交流。

  最后,谢谢阅读。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值