什么是Netty
- 异步事件驱动框架,用于快速开发高性能服务端和客户端
- 封装了JDK底层BIO和NIO模型,提供高度可用的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方法中,return回doBind方法,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底层创建完成的channel,ch.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,然后绑定到channel的config配置中,一个获取用户自定义的属性,然后绑定channel的属性中。然后看下面的:
上标记的操作其实都是一些简单操作,主要也是将用户自定义的选项和属性,用两个局部变量进行保存。属性配置完成之后,拿到服务端的pipline,pipline是在创建serverSocketChannel的时候创建的,把上面保存的两个属性传进去给pipline,代码如下:

pipeline还需要将用户自定义的handler处理链添加进去,配置进去后,在接下来的代码中,我们可以看到netty将自动添加连接器ServerBootstrapAcceptor,也相当于自带的handler,其中后面两个属性,是之前所定义的,而currentChildHandler是netty使用最外层链式调用的childHandler()方法,也是用户自定义的,而currentChildGroup,也是由最外层用户自定义确定的,如下图:
这图上的代码,也是我用来分析Netty的Demo。总的来说,初始化channel,保存用户自定义的属性,根据这zdn属性创建一个连接接入器,这个链接接入器每次accept上新的连接之后,都会使用这些属性对新的连接进行配置。
3. 注册selector
服务端的channel创建初始化完成之后,需要将channel注册到事件轮询器seletor上面去,回到initAndRegister()方法,查看到下面这条语句:
这个方法最终会调用到AbstractChannel的register方法,这个方法也是我们的入口:
AbstractChannel.this.eventLoop = eventLoop这句简单的赋值操作,告诉channel后续所有的IO事件相关的操作,都是交给eventLoop来处理, register0(promise)就是做一个实际的注册,进入register0,会发现它实际就只做了三件事情:

三件事就如同上述标注的一样,关注点主要在register方法上,点进去看abstractNioChannel方法,如下:
从中,我们可以看到netty的注册最终调用的还是jdk底层的register方法,如上面讲述到的,javachannel在创建服务端channel的时候,会创建jdk底层相关的channel,然后对这个channel保存到成员变量里面,保存的代码如下:
然后,我们接着看javachannel的register方法,该方法三个参数,第一个是需要注册到的selector,第二个opts是我们需要关心的事件,此处,不需要关注,输入的0,代表不关心任何事件,只是单独地把channel绑定到selector上面去,最后this属性是就意味着是服务端的channel,当selector轮询到java上面的读写事件,可以直接把这个this,也就是这个绑定的channel,针对这个netty的niochannel做一个事件的传播。
再回头看后面两个事情,其实,可以对应我们demo入口的handler:
进入ServerHandler(),如下:
运行下代码,会看到下面这输出:

这输出,基本上就是对应pipeline的两个过程,但要注意的是channelActive并不是对应pipeline的fireChannelActive方法,因为netty在这个过程只是做一个selector绑定操作,并没有绑定端口,所以isActive返回的是false。
最后,总结下绑定的过程:
先通过AbstractChannel.register(channel)作为一个入口,register方法中,将调用到eventLoop进行线程的绑定,同时调用register0方法进行实际注册,register0方法中,又实践上实现doRegister方法调用jdk底层进行注册,将当前channel绑定到selector上面去,同时调用invokeHandlerAddedIfNeeded方法和fireChannelRegistered方法进行事件传播,最终传播到我们的用户方法。
4. 端口绑定
先来张总结图大纲:
调用doBind()方法,将端口实际绑定到本地,doBind方法将会调用的jdk的javaChannel.doBind()方法,通过jdk底层的channel进行端口的绑定,端口绑定成功后,调用pipeline.fireChannelActive(),传播一个active事件,在传播的时候,会从HeadContext开始触发,HeadContext把这个事件向前传播的时候,最终会调用到HeadContext.readIfIsAutoRead()方法,而这个方法将会触发read事件,而这个read事件通过层层调用,最终会调用到服务端channel的beginRead方法,而这个beginRead方法完成向selector注册一个accept事件。
HeadContext.readIfIsAutoRead()方法将一个之前注册到selector上的事件,重新绑定成accept事件,有新连接进来,selector就会轮询到accept事件,最终就会将这个链接交给netty处理。
进入NioServerSocketChannel类中的doBind方法,如下:
这个方法主要负责两个事件,一个是doBind()方法,一个是调用pipeline方法fireChannelActive底层的事件,其中doBind()方法中代码如下:
要注意的是,当端口绑定之前wasActive的值为false,绑定完成后,isActive()返回为true,此时需要将active事件进行传播,会调用到下面这方法:
即先从HandlerContext方法先调用,传播过程中,会传播到:
点进去看doBeginRead方法:
首先拿到selectionKey,该值就是之前注册服务端channel到selector上去的返回值,接下来拿到selectionKey的感兴趣事件,注册服务端channel上的,实际上是绑定一个0到seletor上去,所以获取到的IntersetOp是0,判断即为获取到的变量是否和获取到的accept事件判断。前面注册一个事件,在这个事件上再增加一个事件。也可理解为,服务端端口绑定成功后,需要告诉seletor,我需要关心一个accept事件,selector如果轮询到一个Accept事件的时候,就会把这个事件交给netty处理。
当你端口绑定完成后,会触发一个active事件,这个active事件最终会调用到channel的一个read事件,read对服务端channel来说,就是我可以读一个新的连接了。
以上就是对netty服务端demo启动源码的整体分析,可能有很多地方没解释周到,大家可以跟进源码进行追踪,共同交流。
最后,谢谢阅读。