3. TCP/IP协议栈:从物理层到应用层的通信基石

做嵌入式通信这些年,我见过不少工程师一上来就调socket,结果数据发不出去、连不上服务器,折腾半天才发现是底层协议没搞明白。TCP/IP协议栈这东西,说白了就是一套“通信规矩”。你想想看,两个设备要聊天,总得约定好谁先开口、用什么语言、数据怎么打包吧?TCP/IP就是干这个的。

3.1 TCP/IP分层模型——每一层各司其职

TCP/IP模型分四层,从上到下依次是:应用层、传输层、网络层、网络接口层。每一层只关心自己的事,下层为上层提供服务。

我个人习惯把TCP/IP模型比作一个快递系统:

  • 应用层:你下单买东西,填好收货地址和商品信息。对应HTTP、MQTT、CoAP这些协议。
  • 传输层:快递公司决定用空运还是陆运,保证包裹不丢、不乱序。对应TCP或UDP。
  • 网络层:快递分拣中心看地址,规划路线,把包裹从一个城市送到另一个城市。对应IP协议。
  • 网络接口层:快递员骑着三轮车把包裹送到你家门口。对应以太网、Wi-Fi、蜂窝网络。

我在项目中遇到过一个问题:模组发数据总是超时,查了半天发现是应用层直接往网络层塞数据,跳过了传输层的封装。嗯,这里要注意——每一层都有自己固定的数据格式,不能跳层操作。

核心要点:数据从应用层往下传,每经过一层就加一个头部(封装);接收端从下往上,每经过一层就剥掉一个头部(解封装)。这叫“对等层通信”。

TCP/IP四层模型与数据封装流程 发送端 应用层 数据(Data) 传输层 TCP头部 + 数据 网络层 IP头部 + TCP头部 + 数据 网络接口层 帧头部 + IP头部 + TCP头部 + 数据 物理介质 (网线/射频/光纤) 接收端 网络接口层 网络层 传输层 应用层

3.2 IP地址与端口——找到设备,找到进程

IP地址解决的是“哪台设备”的问题,端口解决的是“哪个程序”的问题。两者缺一不可。

3.2.1 IP地址

IPv4地址是32位的,通常写成点分十进制,比如 192.168.1.100。IPv6是128位的,写成冒号十六进制,比如 fe80::1。

在蜂窝模组里,IP地址通常是运营商分配的。模组附着网络后,核心网会给它分配一个IP。我记得有一次调试,模组一直拿不到IP,后来发现是SIM卡的APN配置错了。你想想看,APN不对,就像寄快递写错了城市名,包裹永远到不了。

实战建议:在蜂窝模组项目中,优先使用私有APN或专网APN,避免公网IP冲突。如果必须用公网,记得申请固定IP或使用域名解析。

3.2.2 端口号

端口号是16位的,范围0~65535。常用端口有:

端口范围 分类 举例
0~1023 知名端口(Well-known) HTTP:80, HTTPS:443, FTP:21
1024~49151 注册端口(Registered) MQTT:1883, CoAP:5683
49152~65535 动态/私有端口(Dynamic) 客户端临时分配

我曾经踩过一个坑:在模组上跑MQTT,服务端端口设成了1883,但客户内网防火墙把1024以上的端口全封了。嗯,后来改成8883(加密端口)才通。所以做项目前,一定先搞清楚网络策略。

3.3 TCP三次握手与四次挥手——可靠连接的建立与拆除

TCP是面向连接的协议,说白了就是通信前先“打个招呼”,确认双方都在线、都准备好了,再开始传数据。

3.3.1 三次握手

三次握手的流程,我习惯用“打电话”来类比:

  1. 第一次握手(SYN):客户端说“喂,能听到吗?”——发送SYN包,进入SYN_SENT状态。
  2. 第二次握手(SYN+ACK):服务端说“听到了,你能听到我吗?”——发送SYN+ACK包,进入SYN_RCVD状态。
  3. 第三次握手(ACK):客户端说“我也听到了,开始聊吧!”——发送ACK包,双方进入ESTABLISHED状态。

为什么非要三次?两次不行吗?我刚开始学的时候也有这个疑问。你想想看,如果只有两次握手,服务端发完SYN+ACK后就认为连接建立了,但客户端可能根本没收到(比如网络丢包),服务端就会白白等在那里浪费资源。三次握手保证了双方都能确认对方的接收能力。

避坑指南:我曾经在低功耗项目中遇到一个问题——模组每次发数据前都做三次握手,功耗居高不下。后来改成“长连接+心跳保活”,握手只做一次,后续数据复用已有连接,功耗降了40%。

3.3.2 四次挥手

四次挥手是断开连接的过程。还是用打电话来类比:

  1. 第一次挥手(FIN):A说“我说完了,准备挂电话了”——发送FIN包,进入FIN_WAIT_1状态。
  2. 第二次挥手(ACK):B说“好的,我知道了”——发送ACK包,进入CLOSE_WAIT状态。A收到后进入FIN_WAIT_2状态。
  3. 第三次挥手(FIN):B说“我也说完了,挂了吧”——发送FIN包,进入LAST_ACK状态。
  4. 第四次挥手(ACK):A说“好,挂了”——发送ACK包,进入TIME_WAIT状态。B收到后关闭连接。

这里有个关键点:为什么挥手要四次,而握手只要三次?因为握手时,SYN和ACK可以合并成一个包(SYN+ACK)。但挥手时,B可能还有数据没发完,所以不能把ACK和FIN合并,必须分两次发。

TIME_WAIT状态要持续2MSL(最大报文段生存时间,通常2分钟)。我见过有人写代码时没处理好这个状态,导致端口被占用,新连接连不上。嗯,这个坑我踩过,后来在代码里加了SO_REUSEADDR选项才解决。

3.4 UDP协议特点——简单、快速、不可靠

UDP和TCP是传输层的“双子星”,但性格完全不同。UDP的特点可以用三个词概括:无连接、不可靠、低延迟。

对比项 TCP UDP
连接方式 面向连接(三次握手) 无连接(直接发)
可靠性 可靠(重传、确认、排序) 不可靠(尽力而为)
头部开销 20字节 8字节
传输效率 较低(有确认和重传) 较高(无确认)
适用场景 文件传输、网页浏览、邮件 视频直播、DNS查询、IoT传感器

UDP的头部只有8个字节:源端口、目的端口、长度、校验和。没有序列号、确认号、窗口大小这些复杂字段。所以UDP的传输效率很高,但代价是——丢了包它不管,乱序了它也不管。

我在做视频监控项目时,用的就是UDP。为什么?因为视频流可以容忍偶尔丢几帧,但不能容忍延迟。如果用TCP,丢包后重传会导致画面卡顿,用户体验反而更差。

实战建议:在蜂窝模组上,如果传输的是控制指令(比如开关阀门),必须用TCP保证可靠性;如果传输的是周期性传感器数据(比如温度、湿度),用UDP更省流量、更省电。

不过要注意,UDP虽然不可靠,但可以在应用层自己实现可靠性机制。比如CoAP协议就是在UDP之上加了重传和确认,既保留了UDP的低开销,又实现了可靠传输。这个我们在后面的章节会详细讲。

好了,TCP/IP协议栈的核心内容就这些。记住一句话:TCP/IP不是一堆理论,而是你写代码时每一行都要面对的现实。理解分层、搞懂握手、选对协议,你的模组通信就成功了一半。