Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理


Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理

文章插图
什么是写半包
写半包:一份数据,一次发送没有把他全部发送,需要循环发送,那么第一次的操作称为写半包
什么情况下会出现写半包:
发送方发送200byte,但是接收方只能接受100byte,因此发送方只会发送小于100byte的数据 。
说到这里,机智的小伙伴已经想到了这跟TCP滑动窗口和消息中间件中常见的消息堆积是一个道理 。
总的来说:接收方顶不住来自发送方的数据压力 。
Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理

文章插图
 对?.NETty来说就是,这个时刻TCP发送缓冲区满了,无法再接收整包数据,剩下的数据则会通过Channel去监听写操作,当触发写操作的时候,再把这部分数据给带上,那么这部分数据才完整地传输 。
Netty中的写半包处理前提知识:Netty中的网络数据读写,都先经过ByteBuf 
Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理

文章插图
  • 读操作:从ByteBuf中读取数据
  • 写操作:将数据写入到ByteBuf,然后再通过其他方式把ByteBuf的数据写入(#doWrite
Netty中的网路操作都是通过Channel和里面聚合的对象Unsafe对象进行操作,简单介绍一下 。
Channel
Channel的作用:给Netty用来进行网络网络
JDK 也有自己原生的Channel,但是为了方便框架扩展使用,Netty采用的是封装了一层Facade(门面模式) 。
最重要的是能够支持Netty的自定义Channel来应对不同的业务场景 。
Channel会被注册到EventLoop上,在注册的时候定义好感兴趣的事件,他采用的是基于事件触发的方式,当Channel上触发相对应的事件时,就会主动回调通知,然后交给对应的ChannelHandler进行处理 。
由于本篇讲的是写半包,因此不再过多解释 。
Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理

文章插图
总的来说: Channel就是Netty用来处理网络数据流的
回到本篇的主题:写半包
AbstractNioByteChannel
主要负责处理写半包
总的流程如图:
Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理

文章插图
ChannelOutboundBuffer:环形发送数组
  1. 不停地从ChannelOutboundBuffer读取数据,看是否有可以发送的数据
  2. 如果有,并且是ByteBuf类型的,可以选择发送数据
    • 如果一次发送没有发送完,则采取一定次数的循环发送(写半包)
  3. 数据最后还是没有发送完,则会开一条新线程专门进行剩余数据的发送
  4. 在最后会去同步数据写入进度
源码解析 #doWrite
Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理

文章插图
不停地去环形发送数组里面取数据出来
  • 如果是空了,代表发送完了,把写标志位置空(clearOpWrite

Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理

文章插图
如果不是空数据,则判断是不是ByteBuf数据
  • 对其进行强转,若可读字节数是0,代表消息不可读(reidIndex >= writeIndex),则把他在环形发送数组中移除 。
第一次读的时候,会先去获取循环发送次数writeSpinCount 。循环发送次数就是指:第一次发送没有完成时(写半包)进行循环发送的次数 。
给他设置一个阈值,为的就是当循环发送的时候,IO线程会一直尝试写操作,此时IO线程无法处理其他操作,相当于局部阻塞、死锁、假死的情况 。
像这种处理手法非常常见,比如一般我们会给分布式锁设置一个锁的超时时间,除此之外还需要设置一个客户端的超时时间,避免客户端在拿到锁的时候,这把锁已经过期了 。客户端的超时时间会比锁的超时时间要短 。
Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理

文章插图
然后就是进行循环发送了
Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理

文章插图
消息发送操作完成时候,会调用ChannelOutboundBuffer更新发送进度的消息,并且还会判断是否需要写半包处理
Netty:遇到TCP发送缓冲区满了 写半包操作该如何处理

文章插图
如果没有发完,则设置写半包标识位,启动专门的写半包线程继续发后续的消息


推荐阅读