3. 网络层与传输层:IP地址与子网掩码、TCP三次握手与四次挥手、UDP与TCP的选型对比、端口与Socket编程基础

各位工程师朋友,咱们今天聊点实在的。网络层和传输层,说白了就是数据在设备之间“怎么找路”和“怎么安全送达”的问题。我刚开始做嵌入式通信时,总觉得这些协议离底层硬件很远,直到被一个IP冲突的问题折腾了三天三夜……嗯,从那以后我再也不敢小看这两层了。

3.1 IP地址与子网掩码:设备的名片与门牌号

IP地址就像你家的门牌号,子网掩码就是告诉你“哪些邻居跟你住一个小区”。我个人习惯把子网掩码理解成一把尺子——它量出IP地址里哪些位是网络号,哪些是主机号。

举个例子:192.168.1.100/24,这里的/24就是子网掩码的简写,表示前24位是网络号。换算成十进制就是255.255.255.0。这意味着192.168.1.x这个网段里,所有设备都在同一个局域网。

核心公式:

  • 网络地址 = IP地址 & 子网掩码
  • 广播地址 = 网络地址 | (~子网掩码)
  • 可用主机数 = 2^(32-掩码位数) - 2

我在项目中遇到过一个问题:两台设备IP分别是192.168.1.10192.168.1.20,子网掩码都是255.255.255.0,按理说能直接通信。但死活ping不通。查了半天,发现其中一台设备的子网掩码被人改成了255.255.0.0。你想想看,一个认为对方在隔壁,一个认为对方在千里之外,这能通才怪。

避坑指南:我曾经在配置PLC网络时,把子网掩码写成了255.255.255.128,结果导致两个工位之间的设备无法互访。后来才意识到,这个掩码把原本的/24网段切成了两个/25子网,设备被分到了不同的广播域。

3.2 TCP三次握手:建立可靠连接的“三声问候”

TCP三次握手,说白了就是客户端和服务端互相确认“你准备好了吗?”“我准备好了!”的过程。我习惯把它比作打电话:

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

为什么会这样设计?其实是为了防止“过期连接请求”造成混乱。我记得有一次调试一个工业网关,发现设备每隔几分钟就会自动重连一次。抓包一看,原来是服务端收到了一个旧的SYN包,以为客户端要建立新连接。三次握手就是为了避免这种“历史包袱”。

注意:三次握手的核心是“双方都要确认对方的接收能力”。少一次都不行。我曾经见过有人为了省流量,试图用两次握手建立连接,结果数据发出去对方根本没收到——因为服务端不知道客户端是否收到了自己的确认。

3.3 TCP四次挥手:优雅的告别

四次挥手比握手复杂一点,因为TCP连接是全双工的——两边可以同时收发数据。所以断开连接时,每一端都要单独说再见。

  1. 第一次挥手(FIN):客户端说:“我说完了,准备关。”(发送FIN包,进入FIN_WAIT_1状态)
  2. 第二次挥手(ACK):服务端回答:“知道了,但我还有话没说完。”(发送ACK包,进入CLOSE_WAIT状态)
  3. 第三次挥手(FIN):服务端说:“我也说完了,可以关了。”(发送FIN包,进入LAST_ACK状态)
  4. 第四次挥手(ACK):客户端回应:“好的,再见。”(发送ACK包,进入TIME_WAIT状态,等待2MSL后关闭)

这里有个坑:TIME_WAIT状态。我曾经在服务器上看到大量TIME_WAIT连接,导致端口被占满,新连接无法建立。原因是客户端频繁短连接,每次断开后都要等2MSL(约1-4分钟)才能释放端口。解决方案?要么用长连接,要么调整tcp_tw_reuse参数。

关键点:四次挥手之所以是四次,是因为服务端可能还有数据要发送。如果服务端在收到FIN时已经没有数据要发了,可以把第二次和第三次合并,变成三次挥手。但标准实现还是四次。

3.4 UDP与TCP的选型对比:什么时候该用谁?

这个问题我几乎每次培训都会被问到。直接上对比表:

特性 TCP UDP
连接性 面向连接 无连接
可靠性 可靠(重传、排序、流量控制) 不可靠(尽力而为)
速度 较慢(有握手和确认开销) 快(无握手,直接发)
数据边界 字节流,无边界 报文,有边界
典型应用 HTTP、FTP、SSH、MQTT DNS、DHCP、视频流、游戏

我个人习惯这样选型:

  • 必须用TCP的场景:文件传输、远程控制、数据库操作。丢一个字节都不行。
  • 必须用UDP的场景:实时音视频、传感器数据广播、DNS查询。丢几个包无所谓,但延迟必须低。
  • 两可的场景:物联网设备上报数据。如果数据量小、频率低,TCP更稳妥;如果数据量大、实时性要求高,UDP+应用层重传更合适。

我的经验:在工业现场总线中,我见过有人用UDP做Modbus TCP的替代方案。结果因为丢包导致设备状态不同步,差点酿成事故。后来我建议他们改用TCP,虽然延迟高了20ms,但可靠性从99%提升到了99.99%。记住:工业场景,可靠性优先。

3.5 端口与Socket编程基础:应用层的“门牌号”

端口号是传输层用来区分不同应用程序的标识。IP地址找到了设备,端口号找到了设备上的具体应用。就像一栋楼有门牌号,每个房间还有房间号。

端口号范围:

  • 0-1023:知名端口,如HTTP(80)、HTTPS(443)、FTP(21)
  • 1024-49151:注册端口,如MySQL(3306)、Redis(6379)
  • 49152-65535:动态/私有端口,客户端临时使用

Socket编程,说白了就是操作系统提供的一套API,让应用程序能通过IP+端口收发数据。我最早接触Socket是在一个嵌入式Linux项目上,用C语言写了一个简单的TCP服务器:

// 简单的TCP服务器示例(C语言)
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);

bind(server_fd, (struct sockaddr*)&address, sizeof(address));
listen(server_fd, 3);

int client_fd = accept(server_fd, NULL, NULL);
char buffer[1024] = {0};
read(client_fd, buffer, 1024);
printf("收到: %s\n", buffer);
send(client_fd, "Hello from server", 17, 0);
close(client_fd);
close(server_fd);

这段代码虽然简单,但包含了Socket编程的核心流程:创建→绑定→监听→接受→读写→关闭。我在实际项目中,经常需要处理非阻塞IO、多线程并发、超时重连等问题。嗯,这些咱们后面章节再细聊。

注意:端口冲突是个常见问题。我曾经在调试时,发现服务端启动失败,报“Address already in use”。原因是上一个进程没有正常关闭,端口还处于TIME_WAIT状态。解决方案是设置SO_REUSEADDR选项,允许重用端口。

3.6 本章知识体系总览

下面这张图是我自己画的,把本章的核心逻辑串起来了。你一看就明白:

网络层与传输层知识体系 网络层 IP地址:设备标识 子网掩码:网络划分 网络地址 = IP & 掩码 广播地址 = 网络地址 | (~掩码) 核心:寻址与路由 传输层 TCP:可靠、面向连接 UDP:不可靠、无连接 端口号:区分应用 Socket:IP+端口 核心:端到端通信 TCP连接管理 三次握手:建立连接 SYN → SYN+ACK → ACK 四次挥手:断开连接 FIN → ACK → FIN → ACK 核心:可靠传输保障 TCP vs UDP 选型 TCP:文件、控制、数据库 UDP:音视频、广播、DNS Socket编程: 创建→绑定→监听→读写 核心:场景决定协议 IP+端口=Socket → 应用层通过Socket使用TCP/UDP进行通信

这张图把网络层和传输层的关系、TCP连接管理、以及UDP/TCP的选型逻辑都串起来了。你保存下来,以后做方案设计时拿出来对照一下,思路会清晰很多。


好了,这一章的内容就到这里。网络层和传输层是通信协议的“骨架”,理解透了,后面应用层的协议(HTTP、MQTT、Modbus等)学起来就轻松多了。下一章咱们聊聊应用层的那些事儿。

专注资料整理