2. DoIP消息结构:协议版本与类型标识、负载类型与用户数据、DoIP头部解析、消息流控制
好,咱们进入正题。DoIP 说白了就是让诊断数据在以太网上跑。但车上的网络环境比办公室复杂得多,所以消息结构必须设计得严谨。我刚开始接触 DoIP 时,看着那一堆字节头都大了。后来拆开一个个看,发现其实就那么几块东西。
2.1 协议版本与类型标识
每个 DoIP 消息,开头两个字节是固定的。第一个字节叫协议版本号。目前主流用的是 0x02,对应 ISO 13400-2:2012 版本。第二个字节是反向版本号,就是 0xFF 减去协议版本号。嗯,这里要注意:如果客户端发来的版本号服务器不认识,服务器会直接拒绝连接。
协议版本对照表
| 版本值 | 对应标准 | 状态 |
|---|---|---|
| 0x01 | ISO 13400-2:2010 | 已废弃 |
| 0x02 | ISO 13400-2:2012 | 当前主流 |
| 0x03 | ISO 13400-2:2019 | 新项目推荐 |
我个人习惯在项目初期就固定好版本号。曾经有个项目,测试团队用的工具是 0x01 版本,ECU 固件却是 0x02,结果连路由激活都过不去。排查了半天,最后发现是版本不匹配。
2.2 负载类型与用户数据
接下来两个字节是负载类型,也叫 Payload Type。这个字段决定了消息的用途。比如 0x0001 表示路由激活请求,0x8001 表示诊断消息。我列几个常用的:
- 0x0001:路由激活请求/响应
- 0x0002:车辆识别请求/响应
- 0x0003:车辆网络状态请求/响应
- 0x0004:诊断电源模式请求/响应
- 0x8001:诊断消息(UDS over DoIP)
负载类型后面跟着的就是用户数据。比如诊断消息里,用户数据就是 UDS 的请求或响应。你想想看,DoIP 其实就是个信封,UDS 才是信的内容。
避坑指南:我曾经遇到过一个问题——负载类型写对了,但用户数据长度算错了。结果 ECU 一直收不到完整消息。后来我养成了习惯:每次构造消息前,先算一遍总长度,再填长度字段。
2.3 DoIP头部解析
DoIP 头部一共 8 个字节。结构如下:
字节 0: 协议版本号 (1 byte)
字节 1: 反向版本号 (1 byte)
字节 2-3: 负载类型 (2 bytes)
字节 4-7: 负载长度 (4 bytes)
为什么负载长度要占 4 个字节?因为 DoIP 消息最大可以到 4GB。当然实际场景中很少用到这么大,但标准就是这么定的。我记得有一次做远程刷写,一个刷写包拆成了 64KB 一块,负载长度字段刚好用满 4 字节的低位。
解析头部时,我建议按顺序来:
- 先读版本号,判断是否支持
- 再读负载类型,决定后续处理逻辑
- 最后读负载长度,准备接收用户数据
顺序错了容易出问题。比如你先读了负载长度,再判断版本号,万一版本不支持,长度字段可能都是乱码。
2.4 消息流控制
DoIP 的流控制,说白了就是防止发送方太快、接收方来不及处理。它不像 CAN 总线那样有硬件级别的流控,DoIP 是靠软件逻辑实现的。
主要分两种场景:
- TCP 场景:TCP 本身有滑动窗口机制,DoIP 不需要额外流控。但要注意,如果发送方连续发大量诊断消息,接收方的应用层缓冲区可能溢出。我建议在应用层加一个简单的令牌桶。
- UDP 场景:UDP 没有流控,DoIP 定义了一个叫 "Alive Check" 的机制。发送方定期发心跳,接收方回复确认。如果连续几次没收到回复,就认为连接断了。
重要提醒:我曾经在一个项目中,TCP 场景下没做应用层流控。结果刷写时 ECU 处理不过来,丢了好几个诊断响应。从那以后,我每个项目都会在 DoIP 层之上再加一层简单的流控——每发 10 个诊断消息,就等一个确认再继续。
流控制的另一个关键点是超时。DoIP 标准里定义了几个超时时间:
| 超时名称 | 默认值 | 说明 |
|---|---|---|
| T_TCP_Initial | 2秒 | TCP连接建立后等待首个消息 |
| T_TCP_General | 5秒 | 一般消息的响应超时 |
| T_UDP_Announce | 500ms | 车辆声明消息的间隔 |
| T_Alive_Check | 3秒 | 心跳检测间隔 |
这些超时值在实际项目中可以微调。但我不建议改太大,否则诊断仪等响应会等到天荒地老。也不建议改太小,网络稍微抖动一下就会误判超时。
嗯,最后说一句。DoIP 消息结构看着复杂,但拆开来看就四个部分:版本、类型、长度、数据。你只要把这四个部分搞清楚了,后面做远程刷写和 OTA 升级就顺了。我当年也是这么一步步啃过来的。