2.1 核心包速览:
- io.netty.transport (Java nio实现) channel selector bootstrap
- io.netty.transport.epoll/kqueue/unix.common/sctp
- io.netty.codec (编解码,支持各种协议)
- io.netty.codec.dns/haproxy/http/http2/memcache/mqtt/redis/smtp/socks/stomp/xml
- io.netty.handler (Reactor 模型 Handler)
- io.netty.handler.proxy
- io.netty.buffer (多路复用 ->缓冲)
- io.netty.common
- io.netty.resolver (解析)
- io.netty.resolver.dns
2.2 启动服务:
- 主线:
- our thread:
创建selector
创建server socket channel
初始化server socket channel
给server socket channel 从boss group中选择一个NioEventLoop - boss thread:
将server socket channel 注册到选择的NioEventLoop的selector
绑定地址启动
注册接受连接事件(OP_ACCEPT)到selector上 - 启动服务的本质: 初始化selector 启动ServerSocketChannel 绑定端口
Selector selector = sun.nio.ch.SelectorProviderImpl.openSelector()
ServerSocketChannel serverSocketChannel = provider.openServerSocketChannel()
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this)
javaChannel().bind(localAddress, config.getBacklog())
selectionKey.interestOps(OP_ACCEPT)
- our thread:
- 知识点:
- Selector实在new NioEventLoopGroup()(创建一批NioEventLoop)的创建
- 第一次Register并不是监听OP_ACCEPT,而是0:
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this) - 最终监听 OP_ACCEPT 是通过 bind 完成后的fireChannelActive()来触发的
- NioEventLoop是通过Register操作的执行来完成启动的
- 类似ChannelInitializer,一些Handler可以设计成一次性的,用完就移除,例如授权
2.3 构建连接:
- boss thread
NioEventLoop中的selector轮询创建连接事件(OP_ACCEPT)
创建socket channel
初始化 socket channel 并从 worker group中选择一个NioEventLoop - worker thread
将socket channel 注册到选择的NioEventLoop的selector
注册读事件(OP_READ)到selector上 - 知识点:
* 接收连接本质:
selector.select()/selectNow()/select(timeoutMillis)发现OP_ACCEPT事件,处理:
SocketChannel socketChannel = serverSocketChannel.accepot()
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(),0,this);
selectionKey.interestOps(OP_READ)
* 创建连接的初始化和注册是通过pipeline.fireChannelRead在ServerBootstrapAcceptor中完成
* 第一次Register并不是监听OP_READ 而是0:
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this)
* 最终监听 OP_READ 是通过 Register 完成后的fireChannelActive来触发的
* Worker‘s NioEventLoop是通过Register操作执行来启动
* 接收连接的读操作,不会尝试读取更多次((16次)
2.4 接收数据
- 读数据技巧
1.自适应数据大小的分配器(AdaptiveRecvByteBufAllocator)
2.连续读(defaultMaxMessagesPerRead) - 主线:
多路复用器Selector接收到OP_READ事件
处理OP_READ事件:NioSocketChannel.NioSocketChannelUnsafe.read()
分配一个初始的1024字节的byte buffer来接受数据
从Channel接受数据到byte buffer
记录实际接受数据大小,调整下次分配byte buffer大小
触发pipeline.fireChannelRead(byteBuf)把读取到的数据传播出去
判断接受byte buffer 是否满载而归:是,尝试继续读取直到没有数据或满16次
否,结束本轮读取,等待下次OP_READ事件 - 知识点:
* 读取数据本质:sun.nio.ch.SocketChannelImpl#read(java.nio.ByteBuffer)
* NioSocketChannel read() 是读数据 NioServerSocketChannel read() 是创建连接
* pipeline.fireChannelReadComplete() 一次读事件处理完成
* 为什么最多只尝试读取16次:雨露均沾
* AdaptiveRecvByteBufAllocator 对 bytebuf的猜测:放大果断 缩小谨慎 (需要连续2次判断)
2.5 业务处理:
- 触发pipeline.fireChannelRead(byteBuf) 把读取到的数据传播出去
- 知识点:
* 处理业务本质:数据在pipeline中所有的handler的channelRead()执行过程
* Handler要实现 io.netty.channel.ChannelInboundHandler#channelRead(ChannelHandlerContext ctx,Object msg)且不能加注解@Skip才能被执行到
* 中途可退出,不保证执行到TailHandler
* 默认处理线程就是Channel绑定的NioEventLoop线程,也可以设置其他:
pipeline.addLast(new UnorderedThreadPoolEventExecutor(10), serverHandler)
2.6 发送数据:
- 写数据的三种方式
* write 写到一个buffer
* flush 把buffer里的数据发送出去
* writeAndFlush 写到buffer,立马发送
* write和Flush之间有个ChannelOutboundBuffer - 写数据要点:
* 1. 对方仓库爆仓时,送不了的时候,会停止送,协商等电话通知什么时候好了,再送。
Netty写数据,写不进去时,会停止写,然后注册一个OP_WRITE事件,来通知什么时候可以写进去了再写
* 2. 发送数据时,对方仓库都直接收下,这个时候再发送快递时,可以尝试发送更多的快递
Netty批量写数据时,如果想写的都写进去了,接下来的尝试写更能多 调整 maxBytesPerGatherWrite
* 3. 发送快递时,发到某个地方的快递特别多,会连续发,但快递车有限,考虑其他地方
Netty只要有数据要写,且能写的出去,则一直尝试,直到写不出去或者满16次 writeSpinCount
* 4. 揽收太多,发送来不及,爆仓,这时出个告示牌:收不下了,最好过2天再来邮寄
Netty待写数据太多,超过一定的水位线writeBufferWaterMark.high() 会将可写的标志位改成false,让应用端自己做决定要不要发送数据- 主线:
1.Write ———写数据到buffer
ChannelOutboundBuffer#addMessage
2.Flush ——发送buffer里面的数据:
AbstractChannel.AbstractUnsafe#flush
准备数据:ChannelOutboundBuffer#addFlush
发送:NioSocketChannel#doWrite
3. 写本质:
Single write: sun.nio.ch.SocketChannelImol#write(java.nio.ByteBuffer)
gathering write: sun.nio.ch.SocketChannelImpl#write(java.nio.ByteBuffre[],int,int)
4.写数据写不进去时,会停止写,注册一个OP_WRITE事件,来通知什么时候可以写进去
5.OP_WRIET不是说有数据可写,而是说可以写进去,所以正常情况,不能注册,否则一直触发
6.批量写数据时,如果尝试写的都写进去了,接下来会尝试写更多(maxBytesPerGatheringWrite)
7.只要有数据要写,且能写,则一直尝试,直到16次writeSpinCount 写16次还没有写完,就直接schedule一个task来继续写,而不是注册写事件来触发
8.待写数据太多,超过一定的水位线writeBufferWaterMark.high() 会将可写的标志改为false,让应用端自己做决定要不要继续写
9.channelHandlerContext.channel().write() 从TailContext开始执行
channelHandlerContext.write() 从当前的Context开始
- 主线:
2.7 断开连接:
- 主线:
多路复用器Selector 接收到OP_READ事件:
处理OP_READ事件:NioSocketChannel.NioSocketChannelUnsafe.read()
接收数据
判断接受的数据大小是否<0 如果是,说明是关闭,开始执行关闭
关闭channel 包含cancel多路复用器的key
清理消息:不接受新信息,fail掉所有queue中消息
触发fireChannelnactive和fireChannelUnregistered - 知识点:
关闭连接本质:
java.nio.channels.spi.AbstractInterruptibleChannel#close
java.nio.channels.SelectionKey#cancel
要点:
关闭连接,会触发OP_READ方法。读取字节数是-1代表关闭
数据读取进行时,强行关闭,触发IO Exception 进而执行关闭
Channel的关闭包含了SelectionKey的cancel
2.8 关闭服务:
- bossGroup.shutdownGracefully(); workGroup.shutdownGracefully() 关闭所有Group中的NioEventLoop:
修改NioEventLoop的State标志位
NioEventLoop判断State执行退出 - 知识点:
* 关闭服务本质:
* 关闭所有连接及Selector:
java.nio.channels.Selectors#keys
java.nio.channels.spi.AbstractInterruptibleChannel#close
java.nio.channels.SelectionKey#cancel
selector.close()
* 关闭所有线程:退出循环体 for(;😉
* 关闭服务要点:
优雅:DEFAULT_SHUTDOWN_QUIET_PERIOD
可控:DEFAULT_SHUTDOWN_TIMEOUT
先不接活 后尽量干完手头的活 (先关boss 后关worker: 不是100% 保证)