4、Socket编程基础:Socket API概述、地址族与套接字类型、TCP与UDP基础
好,咱们进入第四章。Socket编程,说白了就是让两个程序能隔着网络说话。在QNX车机上,这玩意儿太常用了——从CAN总线数据转发,到诊断服务,再到和云端通信,底层全是Socket。
我个人习惯把Socket理解成一个「门」。你这边开个门,我那边开个门,中间拉根网线(或者走无线),数据就传过来了。嗯,就这么简单。但门怎么开、开多大、数据怎么走,这里头有讲究。
4.1 Socket API概述
Socket API最早是BSD Unix搞出来的,后来成了事实标准。QNX作为POSIX兼容系统,Socket接口和Linux几乎一样。你如果在Linux上写过网络程序,到QNX上基本无缝切换。
核心API就这几个:
- socket():创建套接字,相当于申请一个「门牌号」
- bind():绑定地址和端口,相当于告诉别人「我在这」
- listen():监听连接(TCP专用),相当于「我准备好了,你来找我」
- connect():发起连接(TCP专用),相当于「我来了,开门」
- accept():接受连接(TCP专用),相当于「好,进来吧」
- send() / recv():收发数据
- close():关闭套接字
重要提醒:QNX下关闭套接字用 close(),不是 closesocket()。Windows程序员刚转过来时经常写错,我见过好几次编译报错一脸懵的。
来看一个最简单的TCP服务端骨架:
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 5);
int client = accept(sockfd, NULL, NULL);
char buf[1024];
recv(client, buf, sizeof(buf), 0);
// 处理数据...
close(client);
close(sockfd);
return 0;
}
这段代码我闭着眼都能写出来。在QNX车机上,我经常把端口号定在5000以上,避免和系统服务冲突。
4.2 地址族与套接字类型
地址族(Address Family)决定了你用哪种「门牌号格式」。常见的就两种:
| 地址族 | 说明 | QNX车机常见场景 |
|---|---|---|
| AF_INET | IPv4地址,32位 | 以太网通信、OTA升级 |
| AF_INET6 | IPv6地址,128位 | 未来车载网络,目前用得少 |
| AF_UNIX | 本地进程间通信 | 同机不同进程通信,性能高 |
套接字类型呢,决定了数据怎么「走」:
- SOCK_STREAM:流式套接字,对应TCP。可靠、有序、无边界。就像打电话——你说一句我听到一句,顺序不乱。
- SOCK_DGRAM:数据报套接字,对应UDP。不可靠、无序、有边界。就像寄明信片——可能丢、可能乱序,但每张明信片是完整的。
- SOCK_RAW:原始套接字,直接操作IP层。车机上很少用,除非你要自己实现协议栈。
我的经验:在QNX车机上,如果你不确定用TCP还是UDP,先选TCP。虽然开销大一点,但调试起来省心。我曾经在某个项目中图省事用了UDP,结果丢包问题查了两天,最后乖乖换回TCP。
4.3 TCP与UDP基础
这两个协议是网络通信的基石。咱们不扯太深,只讲车机上用得着的。
4.3.1 TCP——可靠但慢
TCP是面向连接的。什么意思?通信前先「握手」——三次握手建立连接,四次挥手断开连接。数据发出去,对方没确认就重发。
优点很明显:数据不会丢,顺序不会乱。缺点呢?慢,而且占用资源多。
在QNX车机上,TCP适合这些场景:
- 诊断请求/响应(UDS over TCP)
- OTA升级包下载
- 日志上传
- 配置文件的远程更新
我曾经在调试OTA功能时,发现TCP连接总是超时。查了半天,原来是车机网络不稳定,TCP重传次数太多导致。后来调整了 tcp_retries1 和 tcp_retries2 内核参数才解决。
4.3.2 UDP——快但不可靠
UDP是无连接的。数据发出去就不管了,对方收没收到、顺序对不对,一概不问。就像往人群里扔传单——扔出去就完事。
优点:快,开销小,支持广播和多播。缺点:丢包、乱序、重复,应用层得自己处理。
在QNX车机上,UDP适合这些场景:
- 实时传感器数据(如GPS、IMU)
- 音视频流(丢几帧没关系)
- 服务发现(广播查找设备)
- 心跳包(丢了就丢了,下次再发)
避坑指南:我曾经在车机上用UDP做CAN数据转发,结果发现数据偶尔会重复。原因是UDP包在网络中被复制了。解决方案是在应用层加序列号,接收端去重。嗯,这个坑我踩过,你们别踩了。
4.3.3 怎么选?一张表说清楚
| 对比项 | TCP | UDP |
|---|---|---|
| 可靠性 | 可靠,有确认重传 | 不可靠,发完不管 |
| 顺序 | 保证顺序 | 不保证顺序 |
| 速度 | 较慢 | 较快 |
| 资源占用 | 高(维护连接状态) | 低(无状态) |
| 数据边界 | 无边界(流式) | 有边界(消息独立) |
| 广播/多播 | 不支持 | 支持 |
| 车机典型应用 | OTA、诊断、配置 | 传感器、音视频、心跳 |
你想想看,如果车机上的安全气囊数据用UDP传,丢一个包可能就出大事。所以关键数据必须用TCP。反过来,摄像头画面用TCP传,延迟高到没法看,这时候UDP加一点纠错就够用了。
4.4 QNX下的特殊注意事项
QNX是实时操作系统,网络栈和Linux有些细微差别。我列几个常见的:
- 线程安全:QNX的Socket API是线程安全的,但建议每个线程用独立的套接字,避免锁竞争。
- 非阻塞模式:用
fcntl()设置O_NONBLOCK,车机上常用,防止阻塞主循环。 - 超时设置:用
setsockopt()设置SO_RCVTIMEO和SO_SNDTIMEO,别让程序死等。 - 资源限制:QNX默认文件描述符数量有限,记得用
ulimit查看和调整。
小技巧:在QNX车机上调试网络程序,我习惯先用 netstat -an 看端口占用,再用 sloginfo 看系统日志。这两个命令能解决80%的网络问题。
好了,这一章就到这里。Socket编程是网络通信的基石,你把它搞透了,后面讲TCP/IP协议栈、讲SOME/IP、讲DDS,都会轻松很多。下一章咱们深入TCP连接管理,看看三次握手在QNX下有什么特殊之处。