4、DoIP网络层:IPv4/IPv6地址分配、TCP与UDP端口分配、DoIP报文头部结构

好,咱们进入第四章。这一章讲的是DoIP的「地基」——网络层。

说白了,就是你的诊断仪和ECU怎么在网络上找到对方,用什么端口说话,以及数据包长什么样。我当年刚接触DoIP时,觉得不就是个IP通信嘛,结果在地址分配上栽过跟头。嗯,咱们一步步来。

4.1 IPv4与IPv6地址分配

DoIP支持两种IP版本:IPv4和IPv6。我个人习惯在量产项目中优先用IPv4,因为大多数车载网络还是基于IPv4的。但新平台,尤其是那些要搞V2X的,已经开始推IPv6了。

地址分配这块,有个关键点:DoIP实体必须有一个唯一的IP地址。这听起来是废话,但实际中容易出问题。

核心原则:

  • 每个DoIP节点(ECU或诊断仪)必须分配一个唯一的IP地址
  • IPv4地址通常通过DHCP获取,或者用链路本地地址(169.254.x.x)
  • IPv6地址通过无状态地址自动配置(SLAAC)或DHCPv6

我在项目中遇到过什么情况呢?有一次,两个ECU在同一个网段里,因为DHCP服务器配置错误,拿到了相同的IP地址。结果诊断仪死活连不上其中一个ECU。查了半天,最后用Wireshark抓包才发现IP冲突。所以,我建议你在设计阶段就明确地址分配策略。

我的小技巧:

开发调试阶段,直接用链路本地地址(169.254.x.x)最省事。不需要DHCP服务器,ECU上电后自动协商。但量产时一定要用DHCP,方便网络管理。

4.2 TCP与UDP端口分配

DoIP用了两个传输层协议:TCP和UDP。它们分工明确,各司其职。

UDP端口:13400

这个端口用于车辆发现和节点管理。说白了,就是诊断仪在网络上喊一声「谁在?」,ECU们通过UDP回应。UDP是无连接的,速度快,适合这种广播/多播场景。

TCP端口:13400

这个端口用于实际的诊断通信。一旦诊断仪通过UDP发现了ECU,就会建立TCP连接,然后通过这个连接发送UDS诊断请求和响应。TCP保证数据可靠到达,不会丢包。

你可能会问:为什么两个协议用同一个端口号?嗯,这是ISO 13400标准规定的。端口13400就是DoIP的「门牌号」。

注意:

我曾经见过有人把TCP和UDP端口搞混。记住:车辆发现用UDP,诊断通信用TCP。如果诊断仪用UDP发诊断请求,ECU是不会理你的。

4.3 DoIP报文头部结构

这是本章的重头戏。DoIP报文头部,说白了就是每个数据包前面固定的一段字节,告诉接收方「这是什么类型的消息」。我刚开始看协议文档时,觉得这结构挺绕的。但拆开来看,其实就几个字段。

DoIP报文头部共8字节,结构如下:

字段 长度(字节) 描述
协议版本 1 当前固定为0x02(ISO 13400-2:2012)
逆协议版本 1 协议版本的按位取反,用于校验
负载类型 2 标识报文的具体类型(见下表)
负载长度 4 后续负载数据的字节数(不含头部)

你看,就这么简单。8个字节,四个字段。但每个字段都有讲究。

协议版本和逆协议版本:这两个字段一起用,用来验证通信双方是否支持相同的协议版本。如果接收方发现版本不匹配,直接丢弃报文。我建议你在实现时,一定要做这个校验,否则可能出现诡异的兼容性问题。

负载类型:这是最关键的字段。它告诉接收方「这个报文是干嘛的」。常见的负载类型有:

负载类型值(十六进制) 含义
0x0001 车辆识别请求(UDP)
0x0002 车辆识别响应(UDP)
0x0003 路由激活请求(TCP)
0x0004 路由激活响应(TCP)
0x8001 诊断消息(TCP)

负载长度:这个字段告诉接收方,头部之后还有多少字节的数据。注意,这个长度不包括头部本身的8个字节。我见过有人把整个报文长度填进去,结果接收方解析出错。嗯,这里要注意。

代码示例:构造DoIP报文头部

// 假设我们要发送一个诊断消息(负载类型0x8001)
// 诊断数据长度为100字节

uint8_t doip_header[8];

doip_header[0] = 0x02;                    // 协议版本
doip_header[1] = 0xFD;                    // 逆协议版本 (0xFF - 0x02)
doip_header[2] = 0x80;                    // 负载类型高字节
doip_header[3] = 0x01;                    // 负载类型低字节
doip_header[4] = 0x00;                    // 负载长度高字节
doip_header[5] = 0x00;
doip_header[6] = 0x00;
doip_header[7] = 0x64;                    // 负载长度低字节 (100 = 0x64)

// 然后发送 doip_header + 100字节的诊断数据

避坑指南:

我曾经在解析负载长度时,忘了考虑网络字节序(大端序)。结果在x86小端平台上解析出来的长度完全不对。记住:DoIP头部所有多字节字段都是大端序(网络字节序)。如果你在ARM或x86上开发,记得用htonl()/ntohl()做转换。

4.4 实际调试中的注意事项

最后,分享几个我在调试DoIP网络层时踩过的坑:

  • UDP广播地址:车辆发现请求通常发到255.255.255.255或特定多播地址。确保你的ECU能处理广播包。
  • TCP连接超时:诊断仪和ECU建立TCP连接后,如果长时间没有诊断消息,连接可能会断开。我建议实现一个保活机制(Keep-Alive)。
  • 端口冲突:如果同一个ECU上跑了多个DoIP实例,端口13400可能会被占用。标准允许使用其他端口,但需要事先约定。

好了,这一章就到这里。网络层是DoIP的基石,搞懂了地址、端口和报文结构,后面的路由激活和诊断通信就顺理成章了。下一章咱们聊路由激活——说白了,就是诊断仪怎么跟ECU「握手」建立会话。