本篇文章我们来分析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
事件。