tcp中非阻塞编程为啥出现粘包问题及netty如何解决
在网络编程中我们调用send方法只是将数据存放到sendbuf中由底层tcp进行发送包,这里发送包底层会进行一些优化,尽可能一次发送多的数据,但是数据大小不能太大如果太大会拆分多次进行发送。所有在这个过程中就会出现一次发送包含大量数据包或者一次无法发送完整的数据包。这就是我们说的粘包问题。在阻塞编程中我们获取连接管道后不停去读取数据判断是否满足指定标记来结束一个包,而非阻塞编程中由于select模型会在不同管道中切换需要我们缓存上一次未解析完的包,我们用一个图来讲解:
上图是最简单的select模型,通过上面会发现一个问题,比如1号水龙头的水来了一半出现暂停,2号水龙头来水了,此时用户会先将1号水龙头的水倒掉处理马上再用水桶处理2号水龙头来接水。这样会导致1号还没接完就切换到二号。对应tcp中就是数据包来了一半就被处理了。如何解决这个问题我们可以改进为下图
现在我们添加每个管道一个小水池,每次接完水都是先缓存到对应的小水池中等对应水龙头(这是个高级水龙头)告诉我们这次水流完了,然后我们把水池中的水进行一次处理。对应tcp异步编程就是每个channel接收都创建一个缓存,数据都先存放到缓存池中等最后数据接收完才进行处理
netty是如何解决上面问题了
这个是LengthFieldBaseFrameDecoder的父类中核心l逻辑。并且cumulation为成员变量
现在我们唯一问题找到LengthFieldBaseFrameDecoder,按照上面cumlation应该是每个Channel对应一个,也就是LengthFieldBaseFrameDecoder是每次创建连接时会new一个解码器,是这样吗?
处理新连接核心逻辑在NioMessageUnsafe
进入源码内部,这里是进入NioServerSocketChannel中
这里构建NioSocketChannel对象时会把NioServerSocketChannel对象传入进去。进入NioSocketChannel的构造方法中
通过上面得知每次创建NioSocketChannel都会创建DefaultChannelPipeline。而DefaultChannelPipeline是用来存放Handler对象的
新的连接进来是如何添加到DefaultChannelPipeline中的,ServerBootstrapAccpetor的核心逻辑
我们进入ChannelInitHandler中的channelRegister方法中
到这里我们可以进行总结。在netty中水龙头就是NioSocketChannel,接水人就是NioEventLoop,水桶就是LengthFieldBaseFrameDecoder的成员变量cumulation