3. 网络层与传输层:地址分配、端口号与Socket通信
好,咱们进入第三章节。说实话,很多做诊断的工程师,一开始都只关注应用层的那些诊断服务,比如读故障码、刷写数据。但我要说,网络层和传输层才是DoIP的骨架。你应用层写得再漂亮,如果IP地址配错了,或者端口号搞混了,车上的ECU根本连不上你,那一切都是白搭。
我个人习惯,在开始写任何DoIP代码之前,先把网络层的这张图刻在脑子里。咱们先看个总览,再拆开细讲。
3.1 IPv4与IPv6地址分配
DoIP支持两种IP版本:IPv4和IPv6。你可能会问,现在IPv6还没普及,是不是只用IPv4就够了?嗯,这里要注意,很多新车型,尤其是欧洲车,已经开始强制要求IPv6了。我建议你两个都得懂。
3.1.1 IPv4地址分配
在DoIP的典型场景里,诊断仪和ECU通常是在一个封闭的车辆网络里通信。没有路由器,没有DHCP服务器。那IP地址怎么来?
- 链路本地地址 (Link-Local):这是最常用的方式。ECU上电后,会自动在
169.254.x.x/16这个段里选一个地址。诊断仪也一样。说白了,就是设备自己协商,不需要服务器。 - DHCP:如果车辆网络里有DHCP服务器(比如T-Box),ECU也可以从服务器获取地址。但我在项目里很少见到这种用法,因为增加复杂度了。
- 静态配置:有些老项目,直接给ECU写死一个IP。我不推荐这么做,万一地址冲突,排查起来很头疼。
3.1.2 IPv6地址分配
IPv6在DoIP里越来越重要。它的地址分配方式跟IPv4不太一样。
| 特性 | IPv4 (链路本地) | IPv6 (链路本地) |
|---|---|---|
| 地址范围 | 169.254.0.0/16 | FE80::/10 |
| 地址生成 | 随机选择 + ARP探测 | 基于MAC地址 (EUI-64) 或随机 |
| 冲突检测 | ARP (地址解析协议) | DAD (重复地址检测) |
| 配置方式 | 自动配置 (Auto-IP) | 无状态地址自动配置 (SLAAC) |
你看,IPv6的链路本地地址是自动生成的,通常基于MAC地址。这意味着每个ECU的IPv6地址几乎是唯一的,冲突概率极低。我个人更喜欢IPv6这一点,省心。
3.2 TCP/UDP端口号定义
端口号是传输层的核心。DoIP标准里,端口号定义得非常清楚。你千万别自己乱改。
- UDP 13400:用于车辆发现和节点激活。说白了,就是诊断仪刚上车,不知道有哪些ECU,就通过UDP广播一个请求,ECU们收到后回复自己的信息。
- TCP 13400:用于所有后续的诊断通信。一旦连接建立,所有的UDS请求和响应,都走这个TCP连接。
为什么用两个协议?
UDP是无连接的,适合广播和快速握手。TCP是可靠的,适合传输诊断数据。你想想看,如果诊断刷写时丢了一个包,那整个ECU可能就变砖了。所以,关键数据必须走TCP。
3.3 Socket通信基础
好了,理论说完了,咱们来点实际的。Socket就是操作系统提供的一个编程接口,让你能收发网络数据。在DoIP里,你本质上就是在写Socket程序。
3.3.1 创建Socket
无论是诊断仪还是ECU,第一步都是创建一个Socket。代码很简单,但类型不能搞错。
// 创建UDP Socket (用于车辆发现)
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
// 创建TCP Socket (用于诊断通信)
int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
注意看,SOCK_DGRAM 是UDP,SOCK_STREAM 是TCP。我见过有人把这两个搞混,结果UDP当TCP用,数据收不到还找不到原因。
3.3.2 绑定地址与端口
创建完Socket,需要绑定一个本地地址和端口。ECU通常绑定在 0.0.0.0:13400,意思是监听所有网络接口上的13400端口。
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(13400); // 端口号
addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有接口
bind(tcp_sock, (struct sockaddr*)&addr, sizeof(addr));
3.3.3 监听与连接 (TCP)
ECU作为服务器,需要监听连接请求。诊断仪作为客户端,主动发起连接。
// ECU端:监听
listen(tcp_sock, 5); // 最大挂起连接数
// 诊断仪端:连接
connect(tcp_sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
这里有个细节:DoIP标准规定,一个ECU通常只允许一个诊断仪连接。所以,当第二个诊断仪尝试连接时,ECU应该拒绝。这个逻辑你需要在代码里实现。
3.3.4 数据收发
连接建立后,就是收发数据了。TCP用 send() 和 recv(),UDP用 sendto() 和 recvfrom()。
// TCP发送诊断请求
uint8_t uds_request[] = {0x22, 0xF1, 0x90}; // 读取VIN码
send(tcp_sock, uds_request, sizeof(uds_request), 0);
// TCP接收诊断响应
uint8_t buffer[4096];
int len = recv(tcp_sock, buffer, sizeof(buffer), 0);
3.4 本章小结
这一章的内容,说白了就是三件事:地址怎么配、端口用哪个、Socket怎么写。IPv4和IPv6的地址分配方式不同,但核心都是让设备能互相找到。UDP 13400用于发现,TCP 13400用于诊断。Socket编程是基础,你只要记住创建、绑定、监听、连接、收发这五个步骤就行。
嗯,这里要再强调一下:别小看这些基础。我见过太多人,一上来就写应用层代码,结果网络层配置错了,折腾一整天。先把地基打牢,后面盖楼才稳。