2. 网络基础回顾:TCP/IP协议栈、UDP与TCP的区别、Socket编程基础

好,咱们正式开始之前,先聊聊网络基础。做智能相机,说白了就是让摄像头拍到的画面,通过网络传到你的手机上。这背后全靠网络协议在支撑。我当年刚入行时,觉得这些协议太抽象,直到第一次调通相机画面传输,才真正体会到——嗯,基础不牢,后面全是坑。

2.1 TCP/IP协议栈:分层的思想

TCP/IP协议栈,说白了就是一套通信规则。它把复杂的网络通信拆成四层,每层各司其职。你想想看,从相机镜头到手机屏幕,数据要经过多少环节?不拆开处理,根本没法搞。

这四层分别是:

  • 应用层:咱们写的代码就在这层。比如HTTP、RTSP、MQTT,都是应用层协议。智能相机里,控制指令和视频流都在这层封装。
  • 传输层:负责端到端的可靠传输。TCP和UDP就在这层。我习惯把这层比作快递公司——TCP是顺丰,保证送到;UDP是平邮,丢了不赔。
  • 网络层:负责寻址和路由。IP协议就在这层。数据包怎么从相机找到你的手机?全靠IP地址和路由表。
  • 网络接口层:负责物理传输。比如Wi-Fi、以太网。说白了就是网线和无线信号的事。

我的经验:做嵌入式网络开发,最常打交道的是传输层和应用层。底层驱动一般芯片厂商都帮你搞定了。但如果你要优化传输效率,就得深入理解传输层的行为。我曾经在一个项目中,因为没搞懂TCP的拥塞控制,导致视频流卡顿,排查了整整两天。

2.2 UDP与TCP的区别:选对协议,事半功倍

做智能相机,选TCP还是UDP,这是个灵魂拷问。我见过不少新手,上来就用TCP,结果视频流延迟高得离谱。也见过有人死磕UDP,结果丢包丢到画面全是马赛克。

咱们先看看它们的核心区别:

特性 TCP UDP
连接方式 面向连接(先握手) 无连接(直接发)
可靠性 可靠,保证送达 不可靠,可能丢包
顺序性 保证数据顺序 不保证顺序
传输效率 较低(有确认重传) 较高(无确认)
适用场景 控制指令、文件传输 视频流、音频流、实时数据

为什么会这样?说白了,TCP为了保证数据不丢,做了很多额外工作——三次握手、确认应答、超时重传、拥塞控制。这些机制保证了可靠性,但也带来了延迟。而UDP呢?它就是个莽夫,只管发,不管到没到。

我的建议:在智能相机项目中,我通常这样分配:控制指令(如云台转动、拍照)用TCP,保证指令不丢;视频流用UDP,因为偶尔丢几帧画面,人眼根本看不出来。但如果你要传输关键帧(I帧),最好加个应用层的确认机制。我曾经在一个项目中,因为没处理I帧丢失,导致画面每隔几秒就花屏一次,用户投诉不断。

2.3 Socket编程基础:从理论到实践

Socket,说白了就是网络通信的接口。你写代码时,不用管底层怎么封装、怎么路由,只需要操作Socket这个抽象层就行。我刚开始学Socket时,觉得它就是个文件描述符——打开、读写、关闭,跟操作文件差不多。

咱们看一个最简单的TCP客户端示例:

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket创建失败");
        return -1;
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = inet_addr("192.168.1.100");

    if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("连接失败");
        return -1;
    }

    char *msg = "Hello, Camera!";
    send(sock, msg, strlen(msg), 0);

    char buffer[1024] = {0};
    recv(sock, buffer, sizeof(buffer), 0);
    printf("收到回复: %s\n", buffer);

    close(sock);
    return 0;
}

这段代码做了几件事:

  1. 创建Socket:指定IPv4和TCP协议。
  2. 连接服务器:指定IP和端口。
  3. 发送数据:发一条消息。
  4. 接收回复:等服务器回话。
  5. 关闭连接:用完就关。

避坑指南:我曾经在嵌入式设备上犯过一个低级错误——忘记调用htons()htonl()进行字节序转换。结果在x86开发板上跑得好好的,一部署到ARM相机上,端口号全乱了。记住:网络字节序是大端,而很多嵌入式CPU是小端。不转换,数据就乱套了。

UDP的Socket编程更简单,因为不需要连接:

int sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8080);
dest_addr.sin_addr.s_addr = inet_addr("192.168.1.100");

sendto(sock, msg, strlen(msg), 0, 
       (struct sockaddr*)&dest_addr, sizeof(dest_addr));

// 接收时用recvfrom,可以知道数据从哪来的
recvfrom(sock, buffer, sizeof(buffer), 0, NULL, NULL);

你看,UDP少了connect()这一步,直接发就行。但代价就是——你没法知道数据到底到没到。

2.4 智能相机中的协议选择

在实际项目中,我通常这样设计通信架构:

  • 控制通道:TCP长连接,用于发送拍照、录像、参数设置等指令。必须可靠,一条指令都不能丢。
  • 视频通道:UDP,用于传输H.264/H.265视频流。允许少量丢包,但要求低延迟。
  • 心跳机制:TCP通道上定期发送心跳包,检测设备是否在线。我习惯每5秒发一次,超时15秒判定离线。

我的经验:有一次做户外监控相机,Wi-Fi信号不稳定,TCP控制指令经常超时重传。我后来加了个应用层确认机制——相机收到指令后,回复一个ACK。如果手机端3秒内没收到ACK,就重发。这样虽然增加了协议复杂度,但可靠性大幅提升。说白了,有时候不能完全依赖TCP的可靠性,应用层也得做兜底。

好了,网络基础就回顾到这里。下一章咱们开始实战——用Socket实现一个简单的相机控制协议。到时候我会带着大家一步步写代码,把今天讲的理论用起来。