4、DoIP协议(ISO 13400):TCP/UDP端口分配、车辆发现与路由激活、诊断报文封装与传输
好,咱们进入第四讲。DoIP协议,全称是Diagnostic over Internet Protocol。说白了,就是让诊断数据跑在以太网上。以前咱们诊断用CAN,一根线慢慢传,现在车上的数据量越来越大,OTA升级、远程诊断这些需求一上来,CAN那点带宽就不够看了。DoIP就是干这个的。
我个人习惯把DoIP理解成「汽车界的HTTP」。它定义了诊断仪怎么找到车、怎么建立连接、怎么收发诊断报文。ISO 13400这个标准,就是它的完整规范。嗯,咱们今天不念标准,只讲实战中你必须搞清楚的几个核心点。
4.1 TCP/UDP端口分配:谁用TCP,谁用UDP?
DoIP协议里,端口分配是有明确分工的。我刚开始接触时也犯过糊涂,以为所有数据都走TCP。其实不是。
- UDP 端口 13400:用于车辆发现和车辆声明。说白了,就是广播和组播用的。诊断仪发个「谁在?」的广播,车机收到后回复「我在这,这是我的VIN」。这个过程不需要建立连接,一发一收,UDP最合适。
- TCP 端口 13400:用于路由激活、诊断报文传输。一旦诊断仪找到了车,后续所有正经的诊断通信(比如读故障码、刷写数据)都走TCP。因为TCP可靠,能保证数据不丢、不乱序。
重要:UDP和TCP用的是同一个端口号13400,但协议不同。你抓包时一定要区分清楚。我曾经在项目里看到有人把UDP的车辆发现报文当成TCP报文去解析,结果死活连不上车。
你想想看,为什么车辆发现要用UDP?因为诊断仪刚上电时,根本不知道车在哪。它只能发广播。如果广播也用TCP,那得先建立连接,但连谁都不知道,这就成了死循环。所以UDP在这里是必须的。
4.2 车辆发现与路由激活:怎么找到车?怎么连上车?
车辆发现,DoIP里叫Vehicle Discovery。流程很简单:
- 诊断仪发送一个UDP广播报文(Vehicle Identification Request),目标地址是255.255.255.255:13400。
- 车上的DoIP模块收到后,回复一个Vehicle Identification Response,里面包含VIN、逻辑地址、EID(实体ID)等信息。
- 诊断仪拿到这些信息,就知道车在哪了。
这里有个坑。我曾经遇到过,车机在休眠状态下不会响应UDP广播。你得先唤醒它。唤醒方式可能是CAN报文,也可能是特定的网络管理报文。嗯,这个后面章节会细讲。
找到车之后,下一步是路由激活(Routing Activation)。这一步是为了建立诊断仪和车机之间的逻辑通道。诊断仪发一个Routing Activation Request到TCP 13400端口,车机回复一个Routing Activation Response。只有激活成功后,才能开始传诊断报文。
个人经验:路由激活时,诊断仪需要携带自己的逻辑地址。这个地址不能乱填,必须和OEM定义的地址范围一致。我见过有人随便填了个0x0001,结果车机直接拒绝激活。后来查标准才发现,0x0000到0x00FF是保留地址,不能用于诊断仪。
4.3 诊断报文封装与传输:数据怎么打包?
诊断报文本身是UDS(ISO 14229)的格式。DoIP只是负责把它封装成以太网帧。封装格式如下:
| 字段 | 长度 | 说明 |
|---|---|---|
| 协议版本 | 1字节 | 当前固定为0x02(ISO 13400-2:2012) |
| 反向协议版本 | 1字节 | 一般也是0x02 |
| 负载类型 | 2字节 | 0x8001表示诊断报文(UDS) |
| 负载长度 | 4字节 | 后面跟着的UDS数据长度 |
| 源地址 | 2字节 | 发送方的逻辑地址 |
| 目标地址 | 2字节 | 接收方的逻辑地址 |
| UDS数据 | 可变 | 实际的诊断请求/响应 |
举个例子,你要发送一个读取VIN的UDS请求(22 F1 90),封装后的DoIP报文大概长这样:
02 02 80 01 00 00 00 03 0E 80 00 01 22 F1 90
|--|--|------|-----------|-----|-----|-------|
版本 反向 负载类型 负载长度 源地址 目标地址 UDS数据
这里要注意,负载长度是3,因为UDS数据只有3个字节(22 F1 90)。源地址0x0E80是诊断仪的逻辑地址,目标地址0x0001是车机的逻辑地址。这些地址值每个OEM可能不同,但结构是一样的。
避坑指南:我曾经在调试时发现,车机回复的报文里,源地址和目标地址是反过来的。这是对的,因为响应报文里,车机是源,诊断仪是目标。但有人写代码时忘了交换地址,结果解析出来的数据全乱了。记住:请求和响应,地址要互换。
4.4 传输过程中的注意事项
DoIP报文走TCP,但TCP是流式协议,没有消息边界。你发一个DoIP报文,TCP可能把它拆成多个段发送,也可能把多个报文合并成一个段。所以接收方必须自己组包。
怎么组包?靠负载长度字段。你先读8个字节的头部(版本、反向版本、负载类型、负载长度),然后根据负载长度字段的值,再读那么多字节的UDS数据。这样就能完整还原一个DoIP报文。
我建议你在代码里维护一个状态机:
- 状态0:等待头部(8字节)
- 状态1:等待UDS数据(根据头部中的长度字段)
- 状态2:报文完整,交给上层处理
嗯,这个状态机看起来简单,但实际项目中很容易出bug。比如TCP粘包时,你可能在一个recv()调用里收到了两个完整的DoIP报文。这时候你的状态机必须能连续处理,不能只处理一个就退出。
好了,这一章的内容就这些。DoIP的核心就是三件事:UDP找车、TCP激活、封装传输。搞清楚了这三步,你就能在以太网上跑诊断了。下一章咱们聊聊实际抓包分析,看看报文长什么样。