首页 > 应用开发 > Go语言 > JT808分包处理:那些标准没说清楚的事
JT808 协议里关于分包的定义,只有短短几句话:
当消息体属性中的长度大于 1024 字节时,终端应分包发送。分包消息的消息体前 4 字节为消息包总数和包序号。
标准没说清楚的事太多了。
问题一:分包大小是多少?
标准说“大于 1024 字节时分包”,但没说分包的单位是多少。
我一开始以为是按 1024 字节分包,后来发现不同终端厂商的实现不一样。有的按 1024 字节,有的按 512 字节,有的按 256 字节。更离谱的是,有些终端按“行”分包,比如一条位置数据一行,几行凑够一个包就发。
这就导致一个问题:你不知道下一个包什么时候来,也不知道要等多少个包才能拼出完整消息。
问题二:分包超时怎么处理?
如果终端发送了第 1 包和第 2 包,但第 3 包丢了,怎么办?
标准没说。你只能自己设超时。但超时设多少?设短了,终端网络慢,正常分包还没发完就被丢弃;设长了,内存里积压一堆半成品包,迟早 OOM。
JTE 在
pkg/jt808 里维护了一个
PacketCache,以终端手机号为 key,缓存分包消息。超时时间默认是 30 秒,可配置。
type PacketCache struct {
total uint16
received map[uint16][]byte
lastSeen time.Time
}
每次收到分包,更新
lastSeen。后台 goroutine 定期扫描,超过 30 秒还没收齐的,清理掉。
问题三:分包序号从几开始?
标准说“包序号”,但没说从 0 还是从 1 开始。
我见过从 0 开始的,也见过从 1 开始的。更坑的是,同一厂商的不同型号终端,有的从 0 开始,有的从 1 开始。
你只能做一个兼容:无论从 0 还是从 1,都按序拼接。
func (c *PacketCache) Add(seq uint16, data []byte) ([]byte, bool) {
c.received[seq] = data
if len(c.received) == int(c.total) {
var full []byte
for i := uint16(1); i <= c.total; i++ {
if p, ok := c.received[i]; ok {
full = append(full, p...)
} else if p, ok := c.received[i-1]; ok {
full = append(full, p...)
}
}
return full, true
}
return nil, false
}
这段代码很丑,但能跑。后来 JTE 把序号基准做成了配置项,不同厂商配不同的基准。
问题四:分包消息 ID 相同吗?
标准说“分包消息的消息体前 4 字节为消息包总数和包序号”,但没说分包消息的消息 ID 是否相同。
实际上,同一个完整消息的所有分包,消息 ID 是一样的。但消息流水号呢?
标准没说。有的终端所有分包用同一个流水号,有的每个分包用不同的流水号。
JTE 的处理方式是:以终端手机号 + 消息 ID+ 流水号为 key 做缓存。如果流水号不同,就退化为只按手机号 + 消息 ID 缓存,但加一个超时。
问题五:分包和粘包混在一起怎么办?
最恶心的情况:终端发了一个分包,紧接着又发了另一个完整消息。两个消息粘在同一个 TCP 包里。
你需要在解析帧的时候,先按 0x7e 切分,然后对每一帧判断是分包还是完整消息。
JTE 的
FrameReader 在切分帧之后,会检查消息体属性里的分包标志位。如果是分包,交给
PacketCache;如果不是,直接交给业务处理。
总结
JT808 分包处理,标准只给了骨架,血肉要自己填。不同终端厂商的实现差异,让这件事变得特别麻烦。
如果你在做 JT808 解析,建议:
JTE 把这些逻辑封装在
pkg/jt808 和
module-adapter 中,可以作为实现参考。
项目仓库:
Gitee:suoten/jt-engine GitHub:suoten/JTEngine
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70050062/viewspace-3183629/ ,如需转载,请注明出处,否则将追究法律责任。