本篇文章我们来分析Netty服务端与客户端的启动过程。
服务端启动过程
如果我们要使用Netty来开发一个服务,大体上我们的代码会如下所示:
1 | public class NettyServer { |
主要分为以下3步:
- 初始化
EventLoopGroup - 配置
ServerBootstrap - 创建并绑定
Channel
初始化EventLoopGroup
EventLoopGroup本质上来说可以看成是一个线程池。以NioEventLoopGroup为例,它的继承关系如下图所示:

NioEventLoopGroup继承MultithreadEventLoopGroup,它在实例化过程中会调用MultithreadEventLoopGroup的构造函数:
1 | protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) { |
如果我们没有指定线程数,默认维护逻辑处理器核数2倍的线程。
MultithreadEventExecutorGroup中维护了一个EventExecutor[] children,EventExecutor是用于实际处理的线程,数组大小为前面指定的线程数。
EventExecutor由NioEventLoopGroup的newChild方法建立,实际的类型为NioEventLoop:
1 | protected EventLoop newChild(Executor executor, Object... args) throws Exception { |
NioEventLoop是一个单线程的线程池,核心方法是run()方法,一旦线程启动,就会不间断地查询任务队列taskQueue,将taskQueue中的任务按顺序取出并执行。关于NioEventLoop的内容我们在后面详细分析。
MultithreadEventExecutorGroup中维护了一个EventExecutorChooserFactory.EventExecutorChooser chooser,它是一个EventExecutor的选择器,负责从children中选择一个EventExecutor。根据children数组大小的不同,从PowerOfTwoEventExecutorChooser、GenericEventExecutorChooser选择不同的实例。在children中轮询选择EventExecutor。
配置ServerBootstrap
ServerBootstrap用于引导创建服务端Channel。
调用
ServerBootstrap中的public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)方法设置线程池。其中parentGroup是用于处理连接请求的group(即acceptor),childGroup是用于处理事件的group(即client)。调用
AbstractBootstrap中的public B channel(Class<? extends C> channelClass)方法设置channel的类型。配置服务端监听的端口
调用
ServerBootstrap中的public ServerBootstrap childHandler(ChannelHandler childHandler)方法配置消息处理器,由childHandler持有。
创建并绑定服务端Channel
调用AbstractBootstrap的public ChannelFuture bind()方法来创建并绑定服务端Channel,详细的操作在doBind()方法中,主要的步骤有两步:
- 调用
initAndRegister初始化并注册服务端Channel - 调用
doBind0绑定服务端Channel
初始化并注册服务端Channel
初始化并注册服务端Channel的工作在AbstractBootstrap.initAndRegister方法中完成,它有以下几个步骤。
创建服务端Channel
首先调用channelFactory的newChannel()方法创建服务端Channel。前面我们设置了Channel的类型为NioServerSocketChannel,因此这里会根据Channel的类型通过反射的方式新建Channel。NioServerSocketChannel的构造过程如下:
- 调用
newSocket()方法,通过JDK的SelectorProvider.openServerSocketChannel()方法来创建ServerSocketChannel。 - 新建
NioServerSocketChannelConfig,里面保存tcp参数等数据 - 调用父类
AbstractNioChannel的构造方法。传入刚刚创建ServerSocketChannel,将这个ServerSocketChannel保存在变量ch中,之后可以通过javaChannel()方法来获取这个ServerSocketChannel。调用ServerSocketChannel.configureBlocking()方法将ServerSocketChannel设置成非阻塞模式。 - 调用父类
AbstractChannel的构造方法,创建id、unsafe、pipeline。
初始化服务端Channel
接着调用ServerBootstrap.init方法初始化新建的Channel:
- 设置channel的
ChannelOptions和ChannelAttrs - 设置channel的
ChildOptions和ChildAttrs - 调用
Channel.pipeline()方法获取ChannelPipeline pipeline。pipeline在AbstractChannel中维护,类型为DefaultChannelPipeline。ChannelPipeline用于维护ChannelHandler,ChannelHandler保存在DefaultChannelHandlerContext,以链表的形式保存在DefaultChannelPipeline。 - 在pipeline中添加一个
ChannelInitializer,ChannelInitializer在管道注册完成之后,往管道中添加一个ServerBootstrapAcceptor(继承ChannelInboundHandler),它持有对childGroup(bossGroup)和childHandler(NettyServerFilter)的引用,这个处理器的功能就是为accept的新连接分配线程。
注册selector
最后调用config().group().register(channel)注册selector。获取group(这里的group是我们前面设置的bossGroup),调用MultithreadEventLoopGroup.register方法注册Channel。
- 调用
next()方法选择一个线程(EventLoop)。因为前面选择的是bossGroup,因此这里的EventLoop选择的是bossGroup中的EventLoop。 然后调用
SingleThreadEventLoop.register方法。register方法调用promise.channel().unsafe().register(this, promise)注册Channel。unsafe()返回的是NioMessageUnsafe。最终调用的是
AbstractUnsafe.register0方法。- 调用前面新建的
ServerSocketChannel的doRegister方法,将selector注册到jdk的channel上,将当前NioServerSocketChannel作为attachment。 - 调用
pipeline.invokeHandlerAddedIfNeeded(),初始化Channel。 - 调用
pipeline.fireChannelRegistered(),传播注册成功事件
- 调用前面新建的
绑定服务端Channel
调用AbstractBootstrap.doBind0方法,在channel的线程池中绑定地址。
Channel的绑定调用AbstractChannel.bind来完成,其中的调用关系如下:
- AbstractChannel.bind
- DefaultChannelPipeline.bind
- AbstractChannelHandlerContext.bind
- AbstractChannelHandlerContext.invokeBind
- DefaultChannelPipeline.bind
- AbstractUnsafe.bind
NioServerSocketChannel.doBind
最终调用的是
NioServerSocketChannel.doBind方法,其中调用底层的jdk的channel来绑定地址pipeline.fireChannelActive(),传播
channelActive事件最后调用
HeadContext.channelActive方法- 调用
ctx.fireChannelActive()传播active事件 - 这一步很重要,调用
DefaultChannelPipeline.HeadContext.readIfIsAutoRead(),最后调用AbstractNioChannel.doBeginRead()方法在SelectionKey中注册accept事件
- 调用
总结
- 首先调用
newChannel()创建服务端的channel,这个过程实际上是调用JDK底层的API来创建JDK的channel,然后netty将其包装成自己的服务端channel,同时会创建一些基本的组件绑定在此channel上,比如pipeline。 - 然后调用
init()方法初始化服务端channel,这个过程最重要的是为服务端channel添加一个连接处理器。 - 随后调用
register()方法注册Selector,这个过程中netty将JDK底层的channel注册到事件轮询器Selector中,并把netty的服务端channel作为一个attachment绑定到对应的JDK底层channel中。 - 最后调用
doBind()方法,调用JDK底层的API实现对本地端口的监听。绑定成功之后,netty会重新向Selector注册一个accept事件,注册完成后netty就可以接收新的连接了。
客户端启动过程
下面来看看客户端的启动过程。代码如下:
1 | public class NettyClient { |
客户端代码与服务端代码的差异主要在于两点:
- 使用
Bootstrap而不是ServerBootstrap来配置参数,引导创建Channel - 调用
Bootstrap的connect方法建立连接
connect方法中首先新建并注册Channel,这个过程和服务端差不多,只是初始化Channel的时候在pipeline中添加的是自定义的handler,而服务端则是添加了一个ServerBootstrapAcceptor。
然后调用Channel的connect方法,在ChannelPipeline的connect方法中调用AbstractNioUnsafe.connect方法,最终调用的是SocketChannel.connect方法建立连接。返回连接的结果,如果没有立即连上,在selectKey上设置OP_CONNECT事件。