【Java IO详解3】Netty进阶

Reactor模型

  • 管件类和概念
    Channel, ChannelHandler,ChannelHandlerContext
    ChannelHandler的执行规则 INBound,OutBound
    Pipline里的ChannelHandlerContext
    OutBoundHandler也有read,是为了校验,因为OutBound是在前面。

问题

outChannelHandler.write触发fireChannelRead的时候
inHandler会writeAndFlash,会触发outChannelHandler的 write形成死循环。
所以在OutHandler的write里不能fireChannelRead
会OverStackFlow

Bytebuf

Unpooled.xxx

  • 申请方式

ByteBufAllocator heapBuffer() directBuffer()
compositeBuffer()

  • 两套索引

readIndex和writeIndex

调用get* set不会移动索引。调用read, write*会移动相应的索引。
forEachByte 查找。

  • 派生缓冲区

为Bytebuf提供了专门的方式来呈现其内容的试图。通过以下方法被创建:

dumplicate(), slice(), slice(int, int);

Unpooled.unmodifiableBuffer(…);
order(ByteOrder);
readSlice()

每个方法都返回一个新的ByteBuf实例,它具有自己的读索引、写索引和标记索引。
派生的缓冲区和原缓冲区的数据是共享的。

  • 引用计数

release 计数减一。减到0后会进行回收。
减少GC。

池化的对象如果不释放,可能会引起内存溢出。

writeAndFlush会释放资源

SimpleChannelInboundHandler实现了 release。

  • 粘包,半包

图片

假设客户端分别发送了两个数据包D1和D2给服务器,由于服务器一次读取的字节数是不确定的。所以可能会有以下4种情况。
从用户空间到内存空间。累计几个包后进行flash一起发送。

1:服务端分两次读取到两个独立的数据包,分别是D1和D2,没有粘包和拆包。
2:服务端一次接收到了两个数据包,D1和D2粘在一起,被称为TCP粘包。
3:服务端分两次读取到了两个数据包,第一次读取到了完整的D1和D2部分内容,第二次读取到了D2的剩余内容。这个叫做TCP拆包。
4:服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包剩余内容和D2包的完整包。
如果此时服务端TCP接收滑窗非常小,而数据D1和D2比较大,很有可能会发生第五种可能,既服务端分多次才能将D1和D2包接收完成。期间发生多次拆包。

  • TCP粘包/半包发生的原因

图片

TCP的Nagle算法,会合并小包,统一发送。如此,服务端无法区分哪些数据包是需要分开的,这就产生了粘包。

UDP:作为连接不可靠的传输协议。不会对数据包进行合并发送。没有Nogle算法。
UDP的包是 数据+UDP头+IP头一次封装,没有粘包。
分包产生的原因:

IP分片传输导致。传输过程丢失部分包。或者一个包被分成了多个,接收端顺序打乱了。
整理有几种情况:
1:数据大于套接字发送缓冲区。
2:进行MSS大小的TCP分段,MSS最大报文段长度的缩写。MSS是TCP报文段中数据段的最大长度。数据字段加上TCP首部才等于整个TCP报文段。所以MSS并不是TCP报文长度的最大长度。而是:MSS=TCP报文头长度-TCP首部长度。

1、两次请求,每个请求一个包
2、两次请求和成了一个包
3、第二个包被分成了两个包D2-2、D2-1 ,被分割的包D2-1的包有可能会跟D1合成一个包
4、第一个包分成了两个包,第二部分的包D1-2跟D2合成了一个包

  • 如何解决粘包半包问题

1:包尾巴追加分隔符。 LineBasedFrameDecoder和DelimiterBasedFrameDecoder
2:消息定长。不够的补空格FixedLengthFrameDecoder
3:消息协议氛围消息头和消息体,消息头中包含标识消息总长度。通常设计思路为消息头的第一个字段使用int32来表示消息的总长度,LengthFieldBasedFrameDecoder。

  • 编解码
    加密解密\序列化 反序列化