第2章:通信基础回顾:OSI七层模型与TCP/IP四层模型、Socket编程基础(TCP/UDP)、字节序(大端/小端)、数据封装与解封装

各位同学,欢迎来到第二讲。在正式进入嵌入式中间件之前,我们得先把通信的“老底”翻出来晒一晒。你想想看,不管多高级的中间件,底层跑的还是这些基础协议。我见过不少工程师,中间件API调得飞起,一问到数据怎么从A到B的,就卡壳了。嗯,今天我们就来把这层窗户纸捅破。

2.1 OSI七层模型与TCP/IP四层模型:理论派 vs 实战派

先说OSI七层模型。这东西是国际标准化组织(ISO)搞出来的理想模型。说实话,我在学校学的时候也觉得它太抽象。但工作十几年后,我越来越觉得它是个绝佳的“调试框架”。

OSI七层从上到下分别是:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。每一层只干自己的事,下层为上层服务。这种分层思想,说白了就是“各扫门前雪”。

但实际互联网用的是TCP/IP四层模型。它把OSI的上三层(应用层、表示层、会话层)合并成了应用层,把数据链路层和物理层合并成了网络接口层。为什么?因为实战中不需要那么细。我当年做一个物联网网关项目,对接各种传感器,发现很多厂商根本不关心会话层,直接裸传数据。所以TCP/IP模型更接地气。

核心对比:

OSI七层 TCP/IP四层 典型协议
应用层、表示层、会话层 应用层 HTTP、MQTT、CoAP
传输层 传输层 TCP、UDP
网络层 网络层 IP、ICMP
数据链路层、物理层 网络接口层 以太网、Wi-Fi

我个人习惯在画架构图时用OSI七层,因为更清晰;但在写代码时,脑子里只装TCP/IP四层就够了。

通信基础核心知识体系 OSI七层模型 应用层 表示层 会话层 传输层 网络层 数据链路层 物理层 TCP/IP四层模型 应用层 传输层 网络层 网络接口层 Socket编程基础 TCP Socket(面向连接) UDP Socket(无连接) bind() / listen() / accept() send() / recv() 字节序 大端序 / 小端序 数据封装与解封装 逐层加头 / 逐层去头

2.2 Socket编程基础:TCP与UDP

Socket,中文叫“套接字”,是操作系统提供给应用层的编程接口。说白了,它就是网络通信的“门把手”。你拧一下,数据就出去了。

TCP Socket:面向连接,可靠传输。 就像打电话——先拨号(connect),对方接听(accept),然后通话(send/recv),最后挂断(close)。我做过一个远程固件升级项目,必须用TCP,因为丢一个字节,固件就刷成砖了。

UDP Socket:无连接,不可靠但快。 就像对讲机——你直接喊话,对方不一定能收到。适合实时音视频、传感器数据上报。我记得有一次调试一个UDP丢包问题,查了三天,最后发现是接收缓冲区太小。嗯,这种坑踩过一次就记住了。

实战建议: 在嵌入式设备上,如果数据量小且允许偶尔丢包,优先用UDP。TCP的重传机制在弱网环境下反而可能造成网络风暴。我曾在2G模块上吃过这个亏。

TCP Socket 典型代码流程(伪代码)

// 服务端
int sock = socket(AF_INET, SOCK_STREAM, 0);
bind(sock, &addr, sizeof(addr));
listen(sock, 5);
int client = accept(sock, NULL, NULL);
recv(client, buf, sizeof(buf), 0);
close(client);
close(sock);

// 客户端
int sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, &addr, sizeof(addr));
send(sock, data, len, 0);
close(sock);

UDP Socket 典型代码流程(伪代码)

// 服务端
int sock = socket(AF_INET, SOCK_DGRAM, 0);
bind(sock, &addr, sizeof(addr));
recvfrom(sock, buf, sizeof(buf), 0, &from, &fromlen);

// 客户端
int sock = socket(AF_INET, SOCK_DGRAM, 0);
sendto(sock, data, len, 0, &to, sizeof(to));

2.3 字节序:大端与小端

字节序,说白了就是多字节数据在内存里怎么排列。大端序(Big-Endian)把高位字节放在低地址,小端序(Little-Endian)把低位字节放在低地址。

为什么嵌入式工程师必须懂这个?因为不同架构的CPU字节序不同。ARM Cortex-M系列默认小端,但可以配置成大端。PowerPC和网络协议栈(如TCP/IP头部)用大端。我当年第一次做跨平台通信时,发现A板发过来的0x1234,B板收到的是0x3412。查了半天,原来是字节序没转换。

避坑指南: 我曾经在STM32和x86 PC之间通信,直接memcpy了一个结构体发送。结果PC端解析出来的数据全是乱的。后来加上了htonl()和ntohl()转换,问题解决。记住:网络字节序是大端,主机字节序看平台。

常用的转换函数:

  • htonl():主机字节序转网络字节序(32位)
  • htons():主机字节序转网络字节序(16位)
  • ntohl():网络字节序转主机字节序(32位)
  • ntohs():网络字节序转主机字节序(16位)

2.4 数据封装与解封装

数据从应用层往下走,每一层都会加上自己的头部信息。这叫封装。接收端从物理层往上走,每一层剥掉头部,这叫解封装。

举个例子:你通过HTTP发送一个"Hello"。应用层加上HTTP头,传输层加上TCP头(源端口、目的端口、序列号等),网络层加上IP头(源IP、目的IP),数据链路层加上MAC头(源MAC、目的MAC)和尾部FCS校验。到了物理层,变成比特流发出去。

接收端反过来,一层层剥掉头部,最后把"Hello"交给应用层。

数据封装过程(发送端):

  1. 应用层:原始数据 + 应用层头部
  2. 传输层:+ TCP/UDP头部(端口号)
  3. 网络层:+ IP头部(IP地址)
  4. 数据链路层:+ MAC头部(MAC地址)+ FCS尾部
  5. 物理层:比特流

我在调试一个Wi-Fi模块时,发现数据总是发不出去。用抓包工具一看,MAC地址填错了。嗯,这种低级错误,犯过一次就再也不会忘了。

好了,这一讲的内容就是这些。通信基础是嵌入式中间件的“地基”,地基不牢,上面盖什么都白搭。下一讲我们开始进入真正的中间件世界。


公众号:蓝海资料掘金营,微信deep3321