在前面的文章中,我们分别分析了Netty的启动过程以及NioEventLoop
的工作流程。本文我们来分析Netty怎样处理新连接的接入。
经过前文Netty——启动过程分析的描述,我们知道Netty在服务端channel的启动过程中将selector注册到jdk的channel上,并将NioServerSocketChannel
作为attachment。服务端channel绑定过程中注册一个accept
事件,注册完成后Netty就可以接收新的连接了。
OP_ACCEPT事件
当新连接接入时,select会接收到accept
事件。NioEventLoop
的运行过程中,判断是OP_ACCEPT
事件,于是执行以下代码:
1 | if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { |
服务端channel——NioServerSocketChannel
持有的unsafe
是NioMessageUnsafe
。
NioMessageUnsafe.read()
方法首先调用NioServerSocketChannel.doReadMessages
方法:
NioServerSocketChannel.doReadMessages
1 | protected int doReadMessages(List<Object> buf) throws Exception { |
NioServerSocketChannel.doReadMessages
方法主要执行了以下两个步骤:
- 调用
accept
方法从jdk的服务端channel——ServerSocketChannel
中获取jdk的客户端channel——SocketChannel
- 使用jdk的客户端channel新建netty的客户端channel——
NioSocketChannel
NioSocketChannel
的新建完成了以下几件事:
- 创建
id
、unsafe
、pipeline
,其中客户端channel持有的unsafe是NioSocketChannelUnsafe
- 保存jdk的客户端channel
- 保存感兴趣的事件——
SelectionKey.OP_READ
- 设置阻塞模型为
false
,即非阻塞模式 - 新建客户端channel的配置——
NioSocketChannelConfig
。设置TcpNoDelay
为true
。
ServerBootstrapAcceptor.channelRead
回到NioMessageUnsafe.read()
方法,生成客户端channel——NioSocketChannel
之后,调用DefaultChannelPipeline.fireChannelRead
方法传播读事件。
事件传播到ServerBootstrapAcceptor.channelRead
方法。这是因为服务端Channel初始化过程中在channel的pipeline中添加了一个ServerBootstrapAcceptor
。
1 | public void channelRead(ChannelHandlerContext ctx, Object msg) { |
channelRead
方法首先将childHandler
中添加到pipeline中,并且设置childOptions
。
然后调用childGroup.register(child)
注册客户端channel,childGroup
是我们在程序启动时新建的工作线程组(NioEventLoopGroup
)。这个方法比较关键,将客户端channel分配到工作线程中去执行。
1 | public ChannelFuture register(Channel channel) { |
next()
调用chooser
在线程组中挑选一个线程来执行register
操作。执行NioEventLoop
的父类SingleThreadEventLoop
的register
方法:
1 | public ChannelFuture register(Channel channel) { |
最后调用客户端channel持有的unsafe——NioSocketChannelUnsafe
的register方法,在eventLoop线程中执行NioSocketChannelUnsafe.register0
方法。
NioSocketChannelUnsafe.register0
调用
NioSocketChannel
父类AbstractNioChannel
的doRegister
方法。在jdk的channel上注册selector,将客户端channel——NioSocketChannel
作为attachment调用pipeline的
fireChannelRegistered
方法传播注册事件调用pipeline的
fireChannelActive
方法传播active事件HeadContext.channelActive
方法:1
2
3
4
5public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
readIfIsAutoRead();
}- 继续传播active事件
readIfIsAutoRead
方法调用TailContext.read
方法TailContext.read
方法调用NioSocketChannelUnsafe
的beginRead
方法,beginRead
方法调用NioSocketChannel.doBeginRead()
方法。NioSocketChannel.doBeginRead
方法注册感兴趣的读事件。
OP_READ事件
NioEventLoop
的运行过程中,判断是OP_READ
事件,和OP_ACCEPT
事件一样执行以下代码:
1 | if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { |
注意,此时程序是在工作线程中执行的,当前的channel是客户端channel——NioSocketChannel
,unsafe对应的是NioSocketChannelUnsafe
。
其read
方法执行步骤如下:
- 分配
ByteBuf
- 调用
doReadBytes
方法从jdk的channel中读取数据保存在ByteBuf
中 - 调用pipeline的
fireChannelRead
方法传播read事件 - 调用pipeline的
fireChannelReadComplete
方法传播读完成事件
总结
新连接接入的过程充分体现了netty的线程模型:
- 在boss线程中由服务端channel——
NioServerSocketChannel
检测到accept事件 - 使用jdk的客户端channel新建netty的客户端channel——
NioSocketChannel
- 将
NioSocketChannel
注册到工作线程中 - 在
NioSocketChannel
中注册读事件
结果上面的步骤,客户端channel——NioSocketChannel
就可以响应读事件。