2、物理层与传输层:以太网物理层基础、TCP/UDP在DoIP中的应用、Socket通信原理
2.1 以太网物理层基础——别小看这根网线
做DoIP诊断,第一关就是物理层。说白了,你得让数据在线上跑起来。
以太网物理层,我们常说的PHY(Physical Layer),负责把数字信号变成模拟信号扔到线上去。我最早接触车载以太网时,总觉得这跟办公室的以太网没啥区别。后来踩了坑才发现——差别大了去了。
车载以太网用的是单对非屏蔽双绞线,100BASE-T1或者1000BASE-T1。普通以太网是两对线,车载为了减重、降成本,只用一对。嗯,这里要注意:一对线既要发又要收,靠的是回声消除技术。我当年调试一个项目,就因为没注意线缆屏蔽,导致诊断通信时不时断连,查了三天才发现是线束干扰。
- 100BASE-T1:传输距离15米,带宽100Mbps
- 1000BASE-T1:传输距离15米,带宽1000Mbps
- 物理层编码:PAM3(100BASE-T1)或PAM4(1000BASE-T1)
你想想看,诊断报文如果物理层不稳定,上层协议再牛也没用。所以做DoIP开发,第一步就是确认PHY芯片配置正确,Link状态稳定。我习惯在硬件上电后先抓一下PHY的寄存器,看看有没有协商成功。
2.2 TCP/UDP在DoIP中的应用——一个可靠,一个轻快
DoIP协议跑在TCP/IP协议栈之上。这里有两个选择:TCP和UDP。它们分工很明确。
UDP:用于发现和公告
DoIP的车辆发现、车辆公告、路由激活确认这些操作,用的是UDP。为什么?因为UDP无连接,发出去就行,不需要握手。你想啊,诊断仪刚上车,连IP都不知道,怎么建立TCP连接?所以先用UDP广播一下,找到车辆再说。
我记得有一次做实车测试,诊断仪发UDP广播,车辆死活不响应。后来发现是防火墙把UDP端口13400给封了。嗯,这个坑我替你们踩过了。
TCP:用于诊断数据交互
一旦建立了逻辑连接,真正的诊断请求/响应(比如读取DTC、刷写ECU)就走TCP了。TCP可靠,有重传、有确认、有流控。诊断报文丢了可不是闹着玩的,万一刷写过程中丢一帧,ECU可能就变砖了。
DoIP规定:TCP端口也是13400。一个车辆可以同时支持多个TCP连接,但通常建议不超过两个。我见过一个项目,诊断仪开了五个TCP连接同时发请求,结果ECU处理不过来,直接复位了。
2.3 Socket通信原理——说白了就是管道
Socket,中文叫套接字。别被名字吓到,它就是个管道。应用程序通过Socket把数据扔给协议栈,协议栈再扔给网卡发出去。
在DoIP中,我们需要创建两种Socket:
- UDP Socket:用于接收广播和发送响应
- TCP Socket:用于处理诊断连接
我写代码的习惯是:先创建UDP Socket,绑定到端口13400,设置SO_BROADCAST选项。然后创建TCP Socket,同样绑定13400,开始监听。
// 伪代码示例:创建UDP Socket
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(13400);
addr.sin_addr.s_addr = INADDR_ANY;
bind(udp_sock, (struct sockaddr*)&addr, sizeof(addr));
// 创建TCP Socket
int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
bind(tcp_sock, (struct sockaddr*)&addr, sizeof(addr));
listen(tcp_sock, 5); // 最多允许5个挂起连接
这里有个细节:TCP Socket的backlog参数,我建议设成5就够了。设太大没意义,ECU资源有限。我曾经设成10,结果内存爆了。
为什么会这样?因为Nagle算法会把小包攒起来再发,诊断报文通常就几十个字节,一攒就是几百毫秒。诊断仪那边等超时了,你这边还没发出去。嗯,这个坑我当年调了整整一天。
最后说一句:Socket编程的核心就是处理好阻塞和非阻塞。DoIP的TCP连接建议用非阻塞模式,配合select或epoll做事件驱动。别用多线程,ECU资源经不起折腾。
好了,物理层和传输层就聊到这儿。下一节我们讲DoIP的报文格式和路由激活,那才是真正的重头戏。