当前位置: 首页 > news >正文

嵌入式上位机开发入门(十八):修复首次连接超时问题

目录

  • 一、前言
  • 二、整体通信时序
  • 三、accept 的工作原理
  • 四、问题根因分析
  • 五、修复方案
  • 六、修复前后对比
  • 七、总结
  • 八、结尾

一、前言

大家好,这里是Hello_Embed。上篇完成了 AT_Socket 的移植并成功编译。本篇来解决实测时遇到的一个典型问题——首次连接总会出现一闪而过的超时错误

通信架构如下:

Modbus Poll(上位机) ↕ WiFi 热点 W800 模块 ↕ UART 串口 STM32H5(Server)

上位机通过 WiFi 热点连接 W800,W800 再通过串口与开发板通信。正是这里的串口速度瓶颈,埋下了竞态条件的隐患。


二、整体通信时序

完整的 Modbus TCP 通信时序如下:

步骤操作代码
1Server 监听 IP 地址与端口modbus_tcp_listen(ctx, 1)
2Modbus Poll 发起连接请求
3Server 接受连接modbus_tcp_accept(ctx, &s)
4Modbus Poll 发送 Modbus 数据
5Server 读取网络数据modbus_receive(ctx, query)
6Server 回应数据modbus_reply(ctx, query, rc, mb_mapping)
7Modbus Poll 读到回应

Server 监听阶段代码:

Draw_String(0,48,"Waiting connect ...",0xff0000,0);while(1){s=modbus_tcp_listen(ctx,1);if(s>=0)break;}

Server 接受连接阶段代码:

while(1){s=modbus_tcp_accept(ctx,&s);if(s>=0)break;}Draw_String(0,64,"Modbus client connected",0xff0000,0);

主循环——接收并回复

do{rc=modbus_receive(ctx,query);/* Filtered queries return 0 */}while(rc==0);rc=modbus_reply(ctx,query,rc,mb_mapping);

三、accept 的工作原理

要理解这个 Bug,先看modbus_tcp_accept内部的执行流程:

第一步:构造 AT 命令,查询 socket 状态:

sprintf((char*)buf,"AT+SKSTT=%d\r",(int)ptDev->sockets[socket].user_data);

第二步:通过串口执行 AT 命令,等待响应:

err=at_exec_cmd(ptDev,(int8_t*)buf,(uint8_t*)buf,sizeof(buf),&resp_len,AT_TIMEOUT);

第三步:解析返回结果,提取对端 IP、端口和硬件 socket 号:

sscanf((constchar*)start,"%d,%d,\"%[^\"]\",%d,%d,%d",&hw_socket,&status,remote_ip,&remote_port,&local_port,&rx_data);

第四步:为这个连接分配一个软件 socket,并记录相关信息:

new_socket=w800_get_socket(ptDev,hw_socket);xTaskResumeAll();{/* 记录 hw_socket */ptSocket=&ptDev->sockets[new_socket];ptSocket->user_data=(void*)hw_socket;ptSocket->type=SOCK_STREAM;/* 记录 local 地址信息 */memcpy(&ptSocket->local,&ptDev->sockets[socket].local,sizeof(ptSocket->local));/* 记录 remote 地址信息 */sin=(structsockaddr_in*)&ptSocket->remote;ipaddr_aton(remote_ip,&sin->sin_addr);sin->sin_port=htons(remote_port);memcpy(name,&ptSocket->remote,sizeof(*name));*namelen=sizeof(*name);returnnew_socket;}

关键点accept需要通过串口来回两次(发 AT 命令 + 等响应)才能完成 socket 的分配和信息记录。这段时间内,W800 可能已经收到了上位机的第一帧数据。


四、问题根因分析

问题本质是一个竞态条件(Race Condition)

时间轴: ──────────────────────────────────────────────────────────→ 上位机发起连接 │ ├─ W800 收到连接请求 │ ├─ accept 开始执行(需要串口来回查询,耗时较长) │ ├─ 上位机没等 accept 完成,直接发送了第一帧 Modbus 数据 │ ├─ 后台线程收到数据包(+SKTRPT),调用 w800_recv_packet │ → 根据 hw_socket 号查找软件 socket 结构体 │ → 此时 accept 还未执行完,软件 socket 还没分配 │ → 找不到对应的 socket!数据被丢弃! │ └─ accept 执行完毕,软件 socket 分配好了……但已经太迟了

根本原因:串口通信速度远慢于网络通信速度。accept依赖串口往返查询来完成 socket 分配,而上位机在这段空窗期内已经推送了第一帧数据,导致第一帧数据因找不到对应 socket 而被丢弃,上位机显示超时。


五、修复方案

既然问题在于"数据到了,但 socket 还没分配",解决思路就是:在后台线程收到数据包、却找不到对应软件 socket 时,主动给它分配一个

修改w800_recv_packet中的处理逻辑(在后台线程中):

/* 如果根据 hw_socket 找不到软件 socket 结构体,也给它分配一个 */if(socket==-1){socket=w800_socket(AF_INET,0,0);if(socket!=-1){w800_set_hwsocket(socket,hw_socket);}}

同时,accept中的逻辑也需要对应调整——原来是"没有就新建",现在要改为先查找是否已由后台线程分配,有就直接复用

new_socket=w800_get_socket(ptDev,hw_socket);if(new_socket==-1){/* 后台线程未提前分配,这才是全新连接,新建一个 */new_socket=w800_socket(AF_INET,SOCK_STREAM,0);if(new_socket==-1)return-1;}xTaskResumeAll();{/* 记录 hw_socket */ptSocket=&ptDev->sockets[new_socket];ptSocket->user_data=(void*)hw_socket;ptSocket->type=SOCK_STREAM;/* 记录 local 地址信息 */memcpy(&ptSocket->local,&ptDev->sockets[socket].local,sizeof(ptSocket->local));/* 记录 remote 地址信息 */sin=(structsockaddr_in*)&ptSocket->remote;ipaddr_aton(remote_ip,&sin->sin_addr);sin->sin_port=htons(remote_port);memcpy(name,&ptSocket->remote,sizeof(*name));*namelen=sizeof(*name);returnnew_socket;}

六、修复前后对比

修复前修复后
数据包到达时 socket 未分配数据被丢弃后台线程紧急分配 socket,数据保留
accept 执行完毕时新建软件 socket查找已分配的 socket,直接复用
首次连接结果数据丢失,上位机显示超时数据正常接收,连接成功

修复的核心思想:将 socket 分配的时机从"accept 完成时"提前到"数据包到达时",堵上串口延迟造成的时间窗口。


七、总结


八、结尾

本篇完成了首次连接超时 Bug 的排查与修复。后续还将对 Modbus TCP 程序进行进一步改进,使其能够及时感知 socket 连接状态,处理断线重连等异常场景。

Hello_Embed继续带你从原理到实践,掌握嵌入式上位机开发的核心技能,敬请关注~

http://www.jsqmd.com/news/626302/

相关文章:

  • Triton + RISC-V毓
  • Spring IOC 源码学习 声明式事务的入口点冻
  • ESP32/ESP8266工业级WiFi配置门户库
  • 什么年代了怎么还在用bash啊?现代化shell开箱体验: fish, nu, elvish桨
  • 深度解析Agent技术演进路径与未来趋势
  • IOFILE结构体的介绍与House of orange欠
  • MediaCreationTool.bat 深度解析:Windows 11硬件限制突破的技术原理与实战指南
  • SALSA Series Report
  • BMD26M088 RGB点阵模块I²C驱动与寄存器级开发指南
  • 2026年临江鳝丝必吃品牌筛选:正宗乐山临江鳝丝推荐/老字号临江鳝丝店/老牌临江鳝丝店/临江哪家鳝丝最正宗/选择指南 - 优质品牌商家
  • 代码随想录一刷记录Day25——leetcode491.递增子序列
  • 美国能源部(DOE)发布“关键矿产与材料加速器”资助机会
  • Docker化多服务共存:Nginx 443 SNI 实现多 HTTPS 站点与加密通信无缝部署
  • 初步学习c语言指针的一些简单理解
  • 告别调参玄学:手把手教你用TransNeXt-Tiny在ImageNet上复现84.0%的准确率
  • atomic原子操作实现无锁队列
  • 2026年OpenClaw怎么搭建?阿里云6分钟新手部署OpenClaw,千问大模型安装指南
  • NGLedFlasher:嵌入式多LED非阻塞时序控制库
  • 材料冶金是“天坑”?就业超99%,深造超70%,北京科技大学王牌专业正被新能源巨头疯抢!
  • 2026单位复印机租赁服务商盘点:品牌打印机租赁/学校复印机租赁/学校打印机租赁/彩色复印机租赁/选择指南 - 优质品牌商家
  • TA8428双通道H桥驱动芯片硬件设计与mbed底层驱动实现
  • ComfyUI面部修复FaceDetailer参数调优实战
  • Android显示机制深度解析:Surface、SurfaceFlinger与Choreographer如何协同工作
  • MES / WMS / AGV 交互时序图及生产管理模块界面设计清单
  • 当大模型开始控制设备:我是怎么理解 Agent 架构的勤
  • Arduino嵌入式信号处理库:轻量级LPF/HPF/积分/微分滤波器
  • AI代理受限于人类设计的工具边界和孤立体
  • 揭秘MySQL索引分类档
  • 【2026年阿里巴巴集团暑期实习- 4月11日-算法岗-第二题- 凑对】(题目+思路+JavaC++Python解析+在线测试)
  • 计算机毕业设计:Python智慧空气监测与污染物预测系统 Django框架 可视化 数据分析 Prophet时间序列 大数据 大模型 深度学习(建议收藏)✅