4、DoIP诊断通信:诊断请求/响应封装、DoIP头部解析、诊断消息传输流程
好,咱们进入正题。这一章聊的是DoIP最核心的部分——诊断消息怎么发、怎么收、怎么拆包。说白了,就是UDS诊断报文在以太网上怎么跑起来的。
我记得刚接触DoIP那会儿,总觉得它跟CAN上的诊断差不多。后来踩了坑才发现,以太网上的诊断,光是头部解析就能让你头疼半天。嗯,咱们一步步来。
4.1 诊断消息的封装结构
先看整体结构。一个完整的DoIP诊断消息,从外到内分三层:
- 以太网帧头(MAC地址、IP头、UDP/TCP头)
- DoIP头部(协议版本、类型、长度等)
- 诊断消息载荷(UDS请求/响应)
我个人习惯把DoIP头部看作「信封」,诊断消息就是「信纸」。信封上写清楚了这封信是谁发的、发给谁、多长、什么类型。信纸上才是真正要传递的诊断内容。
关键点:DoIP头部固定为8字节(不含载荷)。这8字节里包含了所有路由和控制信息。
4.2 DoIP头部逐字节解析
咱们把DoIP头部拆开来看。一共8个字段,我按顺序讲:
| 字节偏移 | 字段名 | 长度 | 说明 |
|---|---|---|---|
| 0 | 协议版本 | 1字节 | 当前固定为0x02(ISO 13400-2:2012) |
| 1 | 反向协议版本 | 1字节 | 取反,用于校验。0x02的反码是0xFD |
| 2-3 | 载荷类型 | 2字节 | 大端序。0x8001=诊断请求,0x8002=诊断响应 |
| 4-7 | 载荷长度 | 4字节 | 大端序。表示后面诊断消息的字节数 |
这里有个坑,我必须要说。反向协议版本这个字段,很多初学者会忽略。我曾经在项目里遇到一个ECU,它收到请求后死活不响应。查了半天,发现是工具发的反向版本算错了。0x02的反码是0xFD,不是0xFE。你想想看,就这一个字节不对,整个报文就被ECU丢弃了。
注意:DoIP头部的前两个字节(协议版本+反向版本)必须配对。如果ECU发现反向版本不是协议版本的按位取反,会直接丢弃报文,不会回复任何错误码。
4.3 诊断请求的封装流程
咱们走一遍实际流程。假设你要发送一个UDS诊断请求:读取ECU的VIN码(0x22 0xF1 0x90)。
步骤是这样的:
- 先构造UDS请求载荷:
0x22 0xF1 0x90(3字节) - 封装DoIP头部:版本0x02,反向版本0xFD,类型0x8001,长度0x00000003
- 加上TCP/UDP头部,塞进以太网帧里发出去
用代码表示就是:
// 伪代码示例:构造DoIP诊断请求
uint8_t uds_request[] = {0x22, 0xF1, 0x90};
uint32_t payload_length = sizeof(uds_request);
// 填充DoIP头部
doip_header.version = 0x02;
doip_header.inverse_version = 0xFD;
doip_header.payload_type = 0x8001; // 诊断请求
doip_header.payload_length = htonl(payload_length);
// 发送:先发8字节头部,再发3字节UDS载荷
send(socket, &doip_header, 8, 0);
send(socket, uds_request, payload_length, 0);
这里要注意字节序。载荷长度是4字节,网络字节序(大端)。我见过有人直接用本地字节序填进去,结果在x86机器上测试没问题,换到ARM板子上就全乱了。嗯,htonl()这个函数一定要用。
4.4 诊断响应的解析流程
ECU收到请求后,会返回一个诊断响应。响应头的结构和请求完全一样,只是载荷类型变成了0x8002。
举个例子,ECU回复VIN码:
// 收到的原始数据(十六进制)
02 FD 80 02 00 00 00 11 // DoIP头部:类型0x8002,长度17字节
62 F1 90 4C 53 56 38 30 30 30 30 30 30 30 30 30 30 // UDS响应:0x62 + VIN码
解析步骤:
- 读前2字节,校验协议版本和反向版本
- 读2-3字节,确认是0x8002(诊断响应)
- 读4-7字节,得到载荷长度(这里是17)
- 读后续17字节,得到UDS响应数据
- 解析UDS响应:0x62表示肯定响应,后面是VIN码
小技巧:我习惯在解析时先校验版本字段。如果版本不对,直接丢弃,不要继续解析后面的内容。这样可以避免解析到乱码数据导致程序崩溃。
4.5 诊断消息的传输流程
整个传输流程,说白了就是「请求-响应」模式。但DoIP比CAN多了一层路由机制。
我画个简化的流程图:
诊断工具 DoIP网关 ECU
| | |
|--- DoIP诊断请求 -------->| |
| (0x8001) | |
| |--- UDS请求 (CAN/DoIP) -->|
| | |
| |<-- UDS响应 --------------|
|<-- DoIP诊断响应 ---------| |
| (0x8002) | |
这里有个关键点:诊断工具和ECU之间可能隔着DoIP网关。网关负责把DoIP报文转换成ECU能识别的格式(可能是CAN上的UDS,也可能是另一个DoIP节点)。
我曾经遇到一个场景:工具发诊断请求后,等了5秒都没收到响应。查日志发现,网关把请求转发给了错误的ECU地址。原因就是DoIP头部里的目标地址字段填错了。嗯,这个地址字段在后面的章节会详细讲,这里先提个醒。
4.6 常见问题与排查思路
根据我的经验,诊断通信出问题,90%都出在头部解析上。我列几个常见坑:
- 版本不匹配:工具发0x02,ECU只认0x01。这种情况在旧设备上很常见。
- 长度算错:载荷长度包含了不该包含的字节,或者漏算了。我曾经犯过这种错,把头部长度也算进去了,结果ECU一直等后续数据,超时了。
- 类型搞混:把0x8001(诊断请求)写成了0x8003(告警报文)。ECU收到后直接忽略。
- 字节序问题:载荷长度没转网络字节序。这个前面说过了,不再重复。
排查建议:用Wireshark抓包,直接看DoIP头部。如果Wireshark能正确解析出各个字段,说明头部没问题。如果解析出来是乱码,那就是发送端的问题。
最后说一句,诊断消息传输流程本身不复杂,复杂的是各种边界情况。比如报文分片、重传、超时处理。这些咱们后面章节再展开。这一章你只要记住:DoIP头部8字节,版本校验不能省,长度字节序不能错,类型字段要对上。做到这三点,诊断通信基本就稳了。