第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四层就够了。
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"交给应用层。
数据封装过程(发送端):
- 应用层:原始数据 + 应用层头部
- 传输层:+ TCP/UDP头部(端口号)
- 网络层:+ IP头部(IP地址)
- 数据链路层:+ MAC头部(MAC地址)+ FCS尾部
- 物理层:比特流
我在调试一个Wi-Fi模块时,发现数据总是发不出去。用抓包工具一看,MAC地址填错了。嗯,这种低级错误,犯过一次就再也不会忘了。
好了,这一讲的内容就是这些。通信基础是嵌入式中间件的“地基”,地基不牢,上面盖什么都白搭。下一讲我们开始进入真正的中间件世界。
公众号:蓝海资料掘金营,微信deep3321