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

什么是写半包

写半包:一份数据,一次发送没有把他全部发送,需要循环发送,那么第一次的操作称为写半包

什么情况下会出现写半包:

发送方发送200byte,但是接收方只能接受100byte,因此发送方只会发送小于100byte的数据。

说到这里,机智的小伙伴已经想到了这跟TCP滑动窗口和消息中间件中常见的消息堆积是一个道理。

总的来说:接收方顶不住来自发送方的数据压力

对于Netty来说就是,这个时刻TCP发送缓冲区满了,无法再接收整包数据,剩下的数据则会通过Channel去监听写操作,当触发写操作的时候,再把这部分数据给带上,那么这部分数据才完整地传输。

Netty中的写半包处理

前提知识:Netty中的网络数据读写,都先经过ByteBuf

  • 读操作:从ByteBuf中读取数据
  • 写操作:将数据写入到ByteBuf,然后再通过其他方式把ByteBuf的数据写入(#doWrite

Netty中的网路操作都是通过Channel和里面聚合的对象Unsafe对象进行操作,简单介绍一下。

Channel

Channel的作用:给Netty用来进行网络网络

JDK 也有自己原生的Channel,但是为了方便框架扩展使用,Netty采用的是封装了一层Facade(门面模式)。

最重要的是能够支持Netty的自定义Channel来应对不同的业务场景。

Channel会被注册到EventLoop上,在注册的时候定义好感兴趣的事件,他采用的是基于事件触发的方式,当Channel上触发相对应的事件时,就会主动回调通知,然后交给对应的ChannelHandler进行处理。

由于本篇讲的是写半包,因此不再过多解释。

总的来说: Channel就是Netty用来处理网络数据流的

回到本篇的主题:写半包

AbstractNioByteChannel

主要负责处理写半包

总的流程如图:

ChannelOutboundBuffer:环形发送数组

  1. 不停地从ChannelOutboundBuffer读取数据,看是否有可以发送的数据

  2. 如果有,并且是ByteBuf类型的,可以选择发送数据

    • 如果一次发送没有发送完,则采取一定次数的循环发送(写半包)
  3. 数据最后还是没有发送完,则会开一条新线程专门进行剩余数据的发送

  4. 在最后会去同步数据写入进度

源码解析 #doWrite

不停地去环形发送数组里面取数据出来

  • 如果是空了,代表发送完了,把写标志位置空(clearOpWrite

如果不是空数据,则判断是不是ByteBuf数据

  • 对其进行强转,若可读字节数是0,代表消息不可读(reidIndex >= writeIndex),则把他在环形发送数组中移除

第一次读的时候,会先去获取循环发送次数writeSpinCount。循环发送次数就是指:第一次发送没有完成时(写半包)进行循环发送的次数。

给他设置一个阈值,为的就是当循环发送的时候,IO线程会一直尝试写操作,此时IO线程无法处理其他操作,相当于局部阻塞、死锁、假死的情况

像这种处理手法非常常见,比如一般我们会给分布式锁设置一个锁的超时时间,除此之外还需要设置一个客户端的超时时间,避免客户端在拿到锁的时候,这把锁已经过期了。客户端的超时时间会比锁的超时时间要短

然后就是进行循环发送了

消息发送操作完成时候,会调用ChannelOutboundBuffer更新发送进度的消息,并且还会判断是否需要写半包处理

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

总结

写半包问题本质上是:对于接收方来说,来自发送方的数据压力太大了,因此不得不采取的一种降保护措施

可以在发送方进行解决、也可以在接收方进行解决

Netty并没有采取说,遇到TCP缓冲区满了之后,这个数据包就等下一次再等发,而是能发多少就发多少,不够的 下次再发,是一种追求性能的选择。

像消息中间件遇到消息堆积问题,在消接收方(消费者)增大消费的速度,比如:加消费队列或扩充消费者群组等。

又或者限制发送方(生产者)的发送速度,比如TCP的滑动窗口。

所以互联想的技术都是有相关联的,能看到互相的影子。

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的 Spring Boot 整合 Netty 实现 TCP 发送消息的完整代码: 首先,需要在 pom.xml 中添加对 Netty 的依赖: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.53.Final</version> </dependency> ``` 然后,创建一个 Netty 服务端: ```java @Component public class NettyServer { private final EventLoopGroup bossGroup = new NioEventLoopGroup(); private final EventLoopGroup workerGroup = new NioEventLoopGroup(); @Value("${netty.port}") private int port; @PostConstruct public void start() throws Exception { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new NettyServerHandler()); } }); ChannelFuture future = bootstrap.bind(port).sync(); if (future.isSuccess()) { System.out.println("Netty server started on port " + port); } } @PreDestroy public void stop() throws Exception { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } ``` 其中,NettyServerHandler 是自定义的处理器,用于处理接收到的消息。 接下来,创建一个 Netty 客户端: ```java @Component public class NettyClient { private final EventLoopGroup group = new NioEventLoopGroup(); @Value("${netty.host}") private String host; @Value("${netty.port}") private int port; public void send(String message) throws Exception { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new StringEncoder()); } }); ChannelFuture future = bootstrap.connect(host, port).sync(); if (future.isSuccess()) { Channel channel = future.channel(); channel.writeAndFlush(message); channel.closeFuture().sync(); } } @PreDestroy public void stop() throws Exception { group.shutdownGracefully(); } } ``` 最后,在 application.yml 中配置 Netty 的端口和主机: ```yaml netty: port: 8080 host: localhost ``` 然后就可以在需要发送消息的地方注入 NettyClient,并调用 send 方法发送消息了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值