Netty接收超过1024字节

应用场景

使用 Netty 创建 TCP 服务器,和底层硬件进行TCP 交互,底层每次传输1026个字节。

问题描述及复现

但是Netty的TCP服务端接收数据时,第一包只能接收1024个字节,第二包接收2字节。于是猜测是不是 Netty 有什么配置,将字节缓冲区设置成了 1024。于是到百度上查,有的人说将ChannelOption.SO_BACKLOG 设置成单次包传输的字节大小(在我这就是1026)。实际测试情况表明这并不是问题的解决办法。
于是我尝试着发送2056个字节过去,并打印每次接收的数据大小,结果如下
在这里插入图片描述

第一条日志是 1024 字节,第二条日志是 1032 字节。这两次实际上是底层硬件的第一次上传,被 Netty 分包了。
第三条日志对应客户端第二次上传数据
第四条日志对应客户端第三次上传数据

追踪问题原因

可以看到,对于客户端的三次数据上传,Netty 分配的缓冲区大小不是固定的!猜测可能是 Netty 为了节省内存开销而设计的这种机制,接下来进入断点定位问题。
在这里插入图片描述
可以看到这里有个 read 方法,估计和 jdk的 ServerSocket的read方法一个作用,点过去看看
在这里插入图片描述这里分配了一个 ByteBuf 正好是 1024 字节的容量,应该是在 allocHandle.allocate 方法中分配的空间。点进这个方法看看是如何分配的空间。
在这里插入图片描述
在这里插入图片描述可以看到 ioBuffer是根据参数来分配空间的,这样就可以定位到 guess()方法的返回值是关键。
F7步进到方法内部,可以看到这是一个AdaptiveRecvByteBufAllocator 的内部类,它返回一个成员属性nextReceiveBufferSize
在这里插入图片描述

它在被构建的时候指定了三个参数。而nextReceiveBufferSize就是在此处被初始化的。
在这里插入图片描述
getSizeTableIndex()是什么作用呢?可以到AdaptiveRecvByteBufAllocator 的 Doc 文档上看到这么一句话

在这里插入图片描述

这里可以看到,这个类就是实现可变缓冲区大小的。如果上次填满缓冲区,则下次会创建一个更大的缓冲区。Netty 在初始化时,创建了一个缓冲区大小空间值的数组。
在这里插入图片描述

在 Channel被创建时,会调用 AdaptiveRecvByteBufAllocatornewHandle 方法。此时指定的缓冲区大小默认为 1024
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

解决思路

如果说 Netty 默认提供了一个可变的缓冲区大小分配方案,那么我们可不可以改变这个策略呢?从AdaptiveRecvByteBufAllocator开始向上找到根类型,可以最终找到 RecvByteBufAllocator 接口上,查看这个接口的子类,应该会有其他缓冲区大小分配方案。
在这里插入图片描述
这里有一个固定的接收数组空间分配器,现在只要想办法把默认的 AdaptiveRecvByteBufAllocator换成 FixedRecvByteBufAllocator 就可以解决问题了。

现在回到 read 方法中,guess是在 allocate方法中调用的,而 allocate 则是一个RecvByteBufAllocator.Handle类型。

在这里插入图片描述
此时我们找到 recvBufAllocHandle()是如何创建这个 allocHandle的。
在这里插入图片描述

首先调用 config方法,然后调用getRecvByteBufAllocator来创建这个allocHandle。看下 config方法哪来的?
在这里插入图片描述

原来是在 Channel 中设置的呀,那既然有getRecvByteBufAllocator方法,那么肯定也有setRecvByteBufAllocator方法,我们在ChannelInitializer 中来调用下setRecvByteBufAllocator方法,并 new 一个FixedRecvByteBufAllocator来替换AdaptiveRecvByteBufAllocator
在这里插入图片描述

再次重启 Netty 的 Tcp 服务器,发现每次能够完整的接收到 1026字节,把 maxLen 改成 2056之后也是如此,问题解决!

  • 14
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
使用 Netty 接收和发送十六进制数据需要进行编解码器的配置。 首先,创建一个 `ByteToMessageCodec` 类型的编解码器,这个编解码器可以将字节数据转换成对象,或者将对象转换成字节数据。下面是一个将字节数据解码成十六进制字符串的示例代码: ```java public class HexDecoder extends ByteToMessageCodec<String> { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (in.readableBytes() < 2) { return; } // 读取字节数据并转换成十六进制字符串 byte[] bytes = new byte[in.readableBytes()]; in.readBytes(bytes); String hex = Hex.encodeHexString(bytes); out.add(hex); } } ``` 接下来,创建一个 `MessageToByteEncoder` 类型的编码器,这个编码器可以将对象转换成字节数据。下面是一个将十六进制字符串编码成字节数据的示例代码: ```java public class HexEncoder extends MessageToByteEncoder<String> { @Override protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception { // 将十六进制字符串转换成字节数据 byte[] bytes = Hex.decodeHex(msg.toCharArray()); out.writeBytes(bytes); } } ``` 最后,在 Netty 的管道中添加编解码器即可: ```java ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HexDecoder()); pipeline.addLast(new HexEncoder()); ``` 这样就完成了对十六进制数据接收和发送。在使用时,只需要将十六进制字符串作为消息对象传递给 Netty 的 `Channel` 即可。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值