4、DoIP报文结构:通用报文头格式、协议版本与类型字段、负载类型与长度、DoIP报文封装与解析

DoIP 报文长什么样?说白了,就是一堆字节按特定顺序排好。我刚开始接触 DoIP 时,看着抓包工具里的十六进制数据,说实话有点懵。后来摸清了规律,发现它其实挺有章法的。

这一节,咱们就把 DoIP 报文拆开来看。从通用头格式,到每个字段的含义,再到怎么组装和解析。你跟着我走一遍,以后看报文就跟看自己家地址一样熟。

4.1 通用报文头格式

每个 DoIP 报文,开头都固定有 8 个字节的通用头。这 8 个字节就像快递单上的收件人信息,没有它,数据就送不到地方。

字节偏移 字段名称 长度(字节) 说明
0 协议版本 1 当前 DoIP 协议版本号
1 反向协议版本 1 协议版本取反,用于校验
2-3 负载类型 2 标识报文的具体用途
4-7 负载长度 4 后续负载数据的字节数

这 8 个字节,顺序不能乱。我见过一些新手自己拼报文时把字节顺序搞反了,结果 ECU 根本不认。嗯,这里要注意:DoIP 用的是网络字节序,也就是大端模式。

4.2 协议版本与类型字段

协议版本字段,目前 ISO 13400 定义的版本是 0x02。为什么不是 0x01?我记得早期草案确实是 0x01,后来正式发布时改成了 0x02。如果你在抓包时看到 0x01,那基本是原型设备,不是量产件。

反向协议版本,就是把协议版本按位取反。比如 0x02 取反就是 0xFD。这个字段干嘛用?说白了就是做个简单校验,防止报文在传输过程中被干扰。我在项目中遇到过一台网关,它收到报文后会先检查这个字段,对不上就直接丢弃,连日志都不打。排查了好久才发现是发送端把反向版本算错了。

协议版本对照表

版本值 反向值 含义
0x00 0xFF ISO 13400 草案版本(已废弃)
0x01 0xFE 早期草案版本
0x02 0xFD 当前标准版本
0x03-0xFF 对应取反 保留给未来扩展

4.3 负载类型与长度

负载类型占 2 个字节,它告诉接收方:这个报文是干嘛的。是路由激活请求?还是诊断消息?还是车辆信息请求?全靠这个字段区分。

常见的负载类型值,我整理了一张表,你收藏一下,以后查起来方便:

负载类型值 报文名称 方向
0x0001 通用 DoIP 头否定确认 双向
0x0002 车辆识别请求 客户端 → 车辆
0x0004 车辆识别响应 车辆 → 客户端
0x0005 路由激活请求 客户端 → 车辆
0x0006 路由激活响应 车辆 → 客户端
0x8001 诊断消息 双向
0x8002 诊断消息确认 车辆 → 客户端

负载长度占 4 个字节,表示通用头之后还有多少字节。注意,这个长度只算负载本身,不包括通用头的 8 个字节。你想想看,如果负载长度填了 0,那就说明这个报文只有头,没有数据体。

个人习惯:我在写解析代码时,会先读前 8 个字节,然后根据负载长度字段的值,再读相应数量的字节。这样不会多读也不会少读,内存分配也准确。

4.4 DoIP 报文封装与解析

封装,就是把数据按照 DoIP 格式组装起来。解析,就是反过来,从字节流里提取出各个字段。这两个过程互为逆操作。

4.4.1 封装过程

假设我们要发送一条诊断请求,负载类型是 0x8001,诊断数据是 10 03(会话控制请求)。封装步骤如下:

  1. 确定协议版本:0x02
  2. 计算反向版本:0xFD
  3. 设置负载类型:0x80 0x01
  4. 计算负载长度:诊断数据 2 字节 + 逻辑地址 4 字节 = 6 字节
  5. 填充负载数据:源地址 + 目标地址 + 诊断数据

最终拼出来的报文是这样的:

02 FD 80 01 00 00 00 06 0E 80 0E 00 10 03
|--通用头--| |--负载长度--| |--负载数据--|

我来解释一下:
0E 80 是源逻辑地址(测试设备),
0E 00 是目标逻辑地址(ECU),
10 03 就是诊断请求本身。

4.4.2 解析过程

解析时,我习惯按这个流程走:

  1. 读取前 8 字节,提取协议版本和反向版本
  2. 校验反向版本是否正确(协议版本取反后是否匹配)
  3. 提取负载类型,判断报文用途
  4. 提取负载长度,准备接收后续数据
  5. 根据负载类型,按对应格式解析负载数据

我曾经踩过的坑:有一次解析诊断消息时,我忘了检查负载长度是否超过实际接收到的数据长度。结果 ECU 返回了一个很长的响应,我的缓冲区不够大,直接内存越界了。从那以后,我每次解析都会先做长度校验,宁可多写几行代码,也不能让程序崩溃。

4.5 DoIP 报文结构总览

下面这张图,把 DoIP 报文的整体结构画出来了。你一看就明白:通用头是固定的,负载部分根据类型不同而变化。

DoIP 报文结构总览 通用报文头(8 字节) 协议版本(1) | 反向版本(1) | 负载类型(2) | 负载长度(4) 0x02 | 0xFD | 0x8001 | 0x00000006 负载数据(N 字节) 根据负载类型不同,结构也不同 源逻辑地址(2) | 目标逻辑地址(2) | 诊断数据(N) 说明: • 通用头是所有 DoIP 报文都必须有的固定部分 • 负载长度只统计负载数据,不包含通用头的 8 字节

4.6 实战中的注意事项

  • 字节序问题:DoIP 统一使用大端字节序。如果你在 x86 平台上开发,记得用 htons/htonl 转换。
  • 负载长度上限:理论上负载长度最大是 0xFFFFFFFF,但实际中受限于 TCP 缓冲区大小,一般不会超过 64KB。
  • 版本校验:反向协议版本是简单的校验手段,但不要完全依赖它。我建议在应用层再加一层 CRC 校验。
  • 超时处理:发送完报文后,如果在一定时间内没收到响应,要主动重发或报错。这个超时时间,我一般设 2 秒。

一个小技巧:调试时可以用 Wireshark 抓包,然后对照 DoIP 规范逐字节比对。我当年就是这么学会的,虽然慢,但记得牢。