netty 接收到的数据偶尔不完整?

2023-06-13 11:38:05 +08:00
 furaoo

最近在用 netty 开发服务器端,与 4g 设备进行通讯,但是遇到了接收报文偶尔出现不完整的情况, 如下红色方框,每 10 次问询,就会出现一次报文接收不完整的情况。出问题的报文被截断了。(非红框的日志打印的报文长度都正常) 但报文的长度是一样的。也就是说出问题的报文执行了 2 次 channelRead 方法,然后才执行 channelReadComplete 打印 信息接收完毕..... 。请教一下各位这方面的大佬,这是 tcp 拆包问题吗?

这是我的 netty 配置: netty 启动类

这是自定义 ChannelInitializer 类

这是自定义报文处理类

2560 次点击
所在节点    程序员
25 条回复
yazinnnn
2023-06-13 11:43:52 +08:00
我先摆个粘包警察放这儿
dreamlike
2023-06-13 11:44:09 +08:00
不知道你的 encoder 和 decoder 咋写的,感觉是因为没有正确处理流边界发生的
LeegoYih
2023-06-13 11:47:19 +08:00
最关键的 MyEncoder 和 MyDecoder 没贴出来,大概率是没有处理边界了
furaoo
2023-06-13 12:30:30 +08:00
@dreamlike
@LeegoYih
大佬这是 decoder ,我的 encoder 和 decoder 仅仅是 16 进制转换字符串,没做边界处理
![这是 decoder]( https://images.cnblogs.com/cnblogs_com/blogs/786023/galleries/2283648/o_230613042902_%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20230613122815.png "Magic Gardens")
dreamlike
2023-06-13 12:38:28 +08:00
@furaoo 没做边界处理。。。设计个定界的协议吧 如果是能确定固定长度直接用 fixlength 的那个 codec ,如果没有报文中没有特殊符号也可以按特殊符号分割,或者直接做在头部放 body 长度的协议,不要依赖于裸的 channelread 行为
opengps
2023-06-13 12:39:03 +08:00
粘包
LeegoYih
2023-06-13 13:22:30 +08:00
字符串可以用:
new LineBasedFrameDecoder(1024)
new StringDecoder(CharsetUtil.UTF_8)
new LineEncoder(CharsetUtil.UTF_8)

我们项目中常用 Protobuf ,可以用以下两个:
new ProtobufDecoder(prototype);
new ProtobufEncoder();

也可以参考上述源码自己写一个
LeegoYih
2023-06-13 13:40:43 +08:00
ProtobufVarint32LengthFieldPrepender 用于预制报文长度
ProtobufEncoder
ProtobufVarint32FrameDecoder 用于解析报文长度
ProtobufDecoder

另外:如果 Handler 和 Codec 有 @Sharable 修饰,说明其对象无状态或线程安全,可以共享,避免创建重复对象。
否则必须给每一个 Channel 分配一个对象。
nothingistrue
2023-06-13 13:55:12 +08:00
你压根牛没有定义 FrameDecoder 。隐式 FrameDecoder ,你的 MyDecoder ,又不放代码。
furaoo
2023-06-13 14:39:41 +08:00
@LeegoYih 项目中报文内容是 16 进制不定长,解析出来就是第一张图的内容,我接收发送的数据字节只有最多 170 个字节,为啥也会出现粘包拆包问题啊?
furaoo
2023-06-13 14:40:48 +08:00
@dreamlike 报文内容是 16 进制,不同阶段下报文长度不一样,有 ABCDE5 个阶段。但每个阶段的长度都是一样的。
koloonps
2023-06-13 14:45:06 +08:00
"为啥也会出现粘包拆包问题啊" tcp 是个流啊
koloonps
2023-06-13 14:46:34 +08:00
你不想处理这个就用 UDP
furaoo
2023-06-13 14:51:53 +08:00
@koloonps 用,第一次写 netty ,不熟练😂
koloonps
2023-06-13 14:53:02 +08:00
@furaoo 可以参考 JT808 ,github 上有很多开源的
fzls
2023-06-13 14:53:10 +08:00
tcp 只给你保证一个流式数据,不会保证每次接收到的数据是你所发送的数据段,可能只是一部分先到,也可能是跟其他部分一起到。你需要自己做一套在数据分界的流程,最简单的就是每段数据前面用一个固定字节数来记录接下来的数据流的长度

举个例子,每段数据前方 2 个字节表示后面数据长度:
2 10 2 5
10 10 字节的实际数据 5 5 字节的实际数据

在获取到 tcp 的流数据后,通过这个附加的信息来确认数据边界,等到当前数据包都获取到后再发给应用的地方去解析

如果不明白这个到底是为啥的话,建议好好去看看 tcp 和 udp 各提供了怎样的数据传输服务
LeegoYih
2023-06-13 14:58:13 +08:00
@furaoo 字节流就是这样的,在不确定配置的情况下,应该一律视为无边界。
举个不恰当的例子,你想发送 170 字节,可能第一时间实际经过网卡发出去的就 100 字节,剩下的 70 它想等等再发。
另一边接收到有数据进来, 就直接解析了,结果就不完整了。

所以需要分隔符标识、头部预制报文长度等方案,保证接收到完整的报文后再进行解析。
furaoo
2023-06-13 15:07:57 +08:00
@LeegoYih 感谢大佬
Shazoo
2023-06-13 15:20:12 +08:00
tcp 上层协议,究其根本,总归是 tlv 才可以正确解码逻辑数据。

```
tag (固定长度,一般是包类型)| length (包长)|value (包主体内容)
```

当然可以酌情在包结尾加一些校验。好比跟个 checksum 或者 crc 啥的。

发送组包不必说,解码就是个简易状态机搞定 tlv 拆包就好。

流式数据处理就是这么个套路,
hankai17
2023-06-13 15:21:57 +08:00
粘包警察在此

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/948273

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX