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_INETIPv4地址,32位以太网通信、OTA升级
AF_INET6IPv6地址,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_retries1tcp_retries2 内核参数才解决。

4.3.2 UDP——快但不可靠

UDP是无连接的。数据发出去就不管了,对方收没收到、顺序对不对,一概不问。就像往人群里扔传单——扔出去就完事。

优点:快,开销小,支持广播和多播。缺点:丢包、乱序、重复,应用层得自己处理。

在QNX车机上,UDP适合这些场景:

  • 实时传感器数据(如GPS、IMU)
  • 音视频流(丢几帧没关系)
  • 服务发现(广播查找设备)
  • 心跳包(丢了就丢了,下次再发)

避坑指南:我曾经在车机上用UDP做CAN数据转发,结果发现数据偶尔会重复。原因是UDP包在网络中被复制了。解决方案是在应用层加序列号,接收端去重。嗯,这个坑我踩过,你们别踩了。

4.3.3 怎么选?一张表说清楚

对比项TCPUDP
可靠性可靠,有确认重传不可靠,发完不管
顺序保证顺序不保证顺序
速度较慢较快
资源占用高(维护连接状态)低(无状态)
数据边界无边界(流式)有边界(消息独立)
广播/多播不支持支持
车机典型应用OTA、诊断、配置传感器、音视频、心跳

你想想看,如果车机上的安全气囊数据用UDP传,丢一个包可能就出大事。所以关键数据必须用TCP。反过来,摄像头画面用TCP传,延迟高到没法看,这时候UDP加一点纠错就够用了。

4.4 QNX下的特殊注意事项

QNX是实时操作系统,网络栈和Linux有些细微差别。我列几个常见的:

  • 线程安全:QNX的Socket API是线程安全的,但建议每个线程用独立的套接字,避免锁竞争。
  • 非阻塞模式:用 fcntl() 设置 O_NONBLOCK,车机上常用,防止阻塞主循环。
  • 超时设置:用 setsockopt() 设置 SO_RCVTIMEOSO_SNDTIMEO,别让程序死等。
  • 资源限制:QNX默认文件描述符数量有限,记得用 ulimit 查看和调整。

小技巧:在QNX车机上调试网络程序,我习惯先用 netstat -an 看端口占用,再用 sloginfo 看系统日志。这两个命令能解决80%的网络问题。

好了,这一章就到这里。Socket编程是网络通信的基石,你把它搞透了,后面讲TCP/IP协议栈、讲SOME/IP、讲DDS,都会轻松很多。下一章咱们深入TCP连接管理,看看三次握手在QNX下有什么特殊之处。