Netty源码之写入数据
写入数据,是从缓冲区写入到通道中。读取数据和写入数据是相对概念,从通道读取到缓冲区,从缓冲区写入到通道。
Pipeline结构
Pipeline.png
出站:TailContext---->StringEncoder------>HeadContext
写入到通道有两种方式:
1、channelHandlerContext.channel().writeAndFlush("");
通过NioSocketChannel调用writeAndFlush()方法,这个从pipeline的tail尾节点往前执行过滤
出站:TailContext---->StringEncoder------>HeadContext
2、channelHandlerContext.writeAndFlush("");
通过ChannelHandlerContext类执行,从当前节点往前执行
出站:StringEncoder------>HeadContext
channel.writeAndFlush()方法调用
ServerHandler类是自定义的入站类,在调用channelRead0方法读取数据后,经服务端转发到非当前客户端。实现简单聊天室功能。
image.png
1、writeAndFlush
io.netty.channel.AbstractChannel#writeAndFlush(java.lang.Object)
writeAndFlush.png
2、writeAndFlush
io.netty.channel.DefaultChannelPipeline#writeAndFlush(java.lang.Object)
writeAndFlush.png
3、writeAndFlush
io.netty.channel.AbstractChannelHandlerContext#writeAndFlush(java.lang.Object)
writeAndFlush.png
4、writeAndFlush
io.netty.channel.AbstractChannelHandlerContext#writeAndFlush
writeAndFlush.png
5、write
io.netty.channel.AbstractChannelHandlerContext#write
write.png
①、获取tail的前一个出站Pipeline,这里获取到StringEncoder字符串编码器
②、获取字符串解码器的执行器EventExecutor,此时NioEventLoop并没有运行
③、把写任务放到任务队列中,调用safeExecute方法启动NioEventLoop工作线程并执行任务
④、最终启动线程后,会调用StringEncoder.invokeWrite(m, promise);方法
write调用
1、invokeWriteAndFlush
io.netty.channel.AbstractChannelHandlerContext#invokeWriteAndFlush
invokeWriteAndFlush.png
2、write
调用到Head节点的invokeWrite方法
write.png
3、invokeWrite
io.netty.channel.AbstractChannelHandlerContext#invokeWrite
invokeWrite.png
4、invokeWrite0
io.netty.channel.AbstractChannelHandlerContext#invokeWrite0
invokeWrite0.png
5、write
io.netty.channel.DefaultChannelPipeline.HeadContext#write
write.png
6、write
io.netty.channel.AbstractChannel.AbstractUnsafe#write
write.png
①、通过filterOutboundMessage方法把msg传入的缓存类型转换。如果不是非堆内存类型转换成非堆内存(直接内存)
io.netty.channel.nio.AbstractNioByteChannel#filterOutboundMessage
image.png
②、把msg添加到outboundBuffer
io.netty.channel.ChannelOutboundBuffer#addMessage
addMessage.png
ChannelOutboundBuffer对象添加entry,flushedEntry、unflushedEntry、tailEntry指针的变化。
flushedEntry表示已经刷新的,unflushedEntry表示没有刷新的,表示tailEntry尾指针
添加第一个Entry,和添加第一个后再添加entry1,entry2
Entry.png
添加三个entry的ChannelOutboundBuffer的最终指针状态
image.png
flush调用
1、invokeWriteAndFlush
io.netty.channel.AbstractChannelHandlerContext#invokeWriteAndFlush
invokeWriteAndFlush.png
2、flush
io.netty.channel.ChannelOutboundHandlerAdapter#flush
flush.png
3、flush
io.netty.channel.AbstractChannelHandlerContext#flush
flush.png
4、invokeFlush
io.netty.channel.AbstractChannelHandlerContext#invokeFlush
invokeFlush.png
5、invokeFlush0
io.netty.channel.AbstractChannelHandlerContext#invokeFlush0
invokeFlush0.png
6、flush
io.netty.channel.DefaultChannelPipeline.HeadContext#flush
flush.png
7、flush
io.netty.channel.AbstractChannel.AbstractUnsafe#flush
flush.png
①、获取到outboundBuffer对象
②、addFlush方法把没有刷新的unflushedEntry指针数据添加到刷新的flushedEntry指针
image.png
8、flush0();
io.netty.channel.nio.AbstractNioChannel.AbstractNioUnsafe#flush0
flush0.png
9、flush0
io.netty.channel.AbstractChannel.AbstractUnsafe#flush0
flush0.png
10、doWrite
io.netty.channel.socket.nio.NioSocketChannel#doWrite
buffer数据写到到通道SocketChannel
doWrite.png
attemptedBytes = 24
buffer = "java.nio.DirectByteBuffer[pos=8192 lim=8216 cap=16777216]"
指针变化,lim=pos+attemptedBytes
总结:
Netty的写入数据是把ByteBuf写到到Channel通道的过程。
ByteBuf数据缓冲区经历了Channel通道所有Pipeline的出站实现类,这个过程是开启了NioEventLoop的事件线程,用来执行出站任务。
写到通道分两个步骤:首先,调用write方法把所有缓冲区数据封装成Entry加入到ChannelOutboundBuffer。
然后调用刷新方法写入到Channel通道