在嵌入式 Linux 应用层,使用 TCP socket 发送数据时,write()/send() 返回成功是否意味着数据已送达对端?如何确保对端应用层确实收到了数据?
考察点:TCP 协议栈缓冲区机制、可靠传输的误解、应用层确认协议。
参考答案:
-
write()成功仅表示 :数据已从用户空间拷贝到内核 TCP 发送缓冲区,且缓冲区剩余空间足够容纳此次写入。它绝不代表:-
数据已发出网卡。
-
数据已被对端 ACK。
-
对端应用已调用
read()取走数据。
-
-
确保应用层收到的方法:
-
应用层 ACK 协议:接收方在成功处理完业务数据后,主动发回一个确认消息(例如自定义的 "ACK" 包)。发送方必须等待此 ACK 才能认为传输完成。
-
结合
shutdown()与SO_LINGER:在关闭连接前,调用shutdown(sockfd, SHUT_WR)发送 FIN,并配合setsockopt(..., SO_LINGER, ...)设置超时,确保对端关闭了连接(暗示其应用已close(),但这仍不等同于业务处理完成)。 -
心跳与超时重传:对于需要高可靠性的场景(如工业控制),设计请求-响应模式,发送方带序列号,接收方按序确认,超时未收到 ACK 则重传。
-
在一个裸机嵌入式设备上,如何实现一个简单有效的 TCP 粘包/拆包处理逻辑?
考察点:TCP 流式传输特性理解、嵌入式协议栈内存管理。
参考答案:
-
原因:TCP 是流协议,没有消息边界,多次发送的小包可能合并到一个 TCP 段中(粘包),或一个长包被 IP 分片、TCP 分段。
-
常用解决策略(裸机环境):
-
定长消息:规定每个数据包固定长度(如 128 字节),不足补零。解析时从流中逐个切分。缺点:浪费带宽。
-
分隔符协议 :在消息末尾添加特殊字节(如
\r\n或0x00)。接收方在 lwIP 的recv回调中遍历缓冲区寻找分隔符。注意 :裸机环境下避免使用strstr等动态内存操作,应直接基于环形缓冲区状态机扫描。 -
头部定长 + 长度字段(最常见):定义包头结构如下:
text
struct pkt_hdr { uint16_t magic; // 可选,帧头校验 uint16_t len; // 后续负载长度 };接收处理逻辑:
-
状态机初始为
WAIT_HEADER,累积到头部长度后解析len,转入WAIT_PAYLOAD。 -
累积到
len字节后,校验通过则交给应用处理,指针回退并重新进入WAIT_HEADER。
-
- lwIP 特有注意 :
tcp_recv回调可能一次收到多个 TCP 段,必须循环调用pbuf_copy_partial()或直接遍历pbuf链表,将数据拷入内部环形缓冲区,再触发状态机解析,避免在回调中长时间阻塞导致协议栈丢包。
-
嵌入式设备通过 4G 模块联网,使用 MQTT 协议与云端通信。若设备发送一个 PUBLISH 报文(QoS=1)后,在未收到 PUBACK 之前网络断开了,重连后应如何处理未确认的消息?
考察点:MQTT 协议状态机、会话保持机制。
参考答案:
-
关键依赖 :
CONNECT报文中的Clean Session标志。-
若
Clean Session = 0(持久会话):-
服务端会为客户端保存未确认的 QoS 1/2 消息。
-
客户端重连时,只要使用相同的
Client ID,服务端会在CONNACK中返回Session Present = 1。 -
随后服务端会立即重发 之前未确认的 PUBLISH 报文给客户端,客户端应能按原
Packet ID处理并响应 PUBACK。
-
-
若
Clean Session = 1(大多数物联网低功耗设备的默认选择):-
会话状态不保留,服务端在连接断开后丢弃所有未确认消息。
-
客户端必须自行负责重传机制:
-
在应用层实现本地存储队列(如 SPI Flash 或文件系统)。
-
每条 PUBLISH 消息在收到 PUBACK 前保留在队列中,标记为"未确认"。
-
网络恢复并重连 MQTT 后,遍历队列中所有"未确认"消息,按顺序重新 PUBLISH(使用新的
Packet ID)。
-
-
-
在 Linux 系统中,编写一个基于 UDP 的客户端程序,如何检测到对端服务已经崩溃(对端主机未关闭 socket,而是整机掉电)?
考察点:UDP 无连接特性与异常检测的工程方法。
参考答案:
-
UDP 原生缺陷 :UDP 没有连接状态,
sendto()通常成功(只要 ARP 表有记录),不会因对端崩溃而报错。 -
检测策略:
-
ICMP 错误报文监听(被动方式):
-
若对端主机崩溃,当本机继续发送 UDP 数据时,若路径上的路由器或最终主机(ARP 失败)返回
ICMP Port Unreachable或ICMP Host Unreachable,这些错误会被内核记录在 socket 的错误队列中。 -
应用层可通过
recvmsg()配合MSG_ERRQUEUE标志获取挂起的错误,从而得知目的不可达。 -
缺点:如果停止发送数据,将永远得不到通知。
-
-
应用层心跳协议(主动方式):
-
客户端定期发送"心跳请求"UDP 包,对端必须回复"心跳应答"。
-
若连续 N 次心跳未收到应答,判定对端失联。这是工程上最可靠的方法。
-
-
SO_KEEPALIVE无效性 :TCP 的SO_KEEPALIVE选项对 UDP 无效,不可混淆。
-