☰
【ESP32局域网TCP双向3Hz测试】
2026/10/20 2:11:07 网站建设 项目流程

ESP32局域网TCP双向3Hz测试(PlatformIO + Arduino + 回调 + 多线程)

前言

在物联网开发中,ESP32 与服务器的 TCP 双向通讯是基础且核心的功能。很多开发者在测试时会遇到TCP粘包/拆包、通讯延迟高、以及阻塞式代码导致看门狗复位等问题。

本文基于Freenove ESP32-S3 WROOM开发板,在PlatformIO + Arduino框架下,采用FreeRTOS多线程 + 回调函数 + 消息队列 + 互斥锁的架构,实现了一套稳定、低延迟的局域网 TCP 双向通讯方案。经实测,在3Hz(约330ms周期)的高频双向收发下,数据完整无粘连,运行稳定。


一、 硬件与软件环境

  • 开发板:Freenove ESP32-S3 WROOM
  • 开发工具:VSCode + PlatformIO
  • 网络调试工具:NetAssist V5.0.14(PC端做TCP Server)
  • ESP32 库:Arduino Core for ESP32

PlatformIO 配置文件 (platformio.ini)

[env:freenove_esp32_s3_wroom] platform = espressif32 board = freenove_esp32_s3_wroom framework = arduino monitor_speed = 115200

注:monitor_speed建议设为 115200,方便查看日志。


二、 核心架构设计

传统的loop()中直接使用client.read()会阻塞 CPU,导致其他任务无法执行;而频繁拼接String又容易造成内存碎片和栈溢出。

本项目采用了以下架构:

  1. 独立接收线程:专门负责读取 TCP 数据,维护字符数组缓冲区,结合换行符\n与50ms空闲超时机制,完美解决 TCP 粘包问题。
  2. 独立发送线程:从 FreeRTOS 消息队列中取出数据并发送,避免网络拥塞导致主循环卡死。
  3. 互斥锁(Mutex):ESP32 的WiFiClient不是线程安全的,用互斥锁保护 TCP 客户端,防止多线程并发操作导致崩溃。
  4. 回调函数(Callback):网络层与业务层解耦,收到完整指令后自动触发回调,方便后续对接阿里云或控制硬件。

三、 完整测试代码 (src/main.cpp)

请将以下代码复制到你的 PlatformIO 项目中,并替换 WiFi 与服务器 IP 信息:

#include<Arduino.h>#include<WiFi.h>// ==================== 配置区 ====================constchar*ssid="LSH_24G";// 你的 WiFi 名称constchar*password="LSH_2025";// 你的 WiFi 密码constchar*serverIP="192.168.170.162";// PC 的局域网 IPconstuint16_tserverPort=7211;// NetAssist 监听的端口// ==================== 全局变量 ====================WiFiClient tcpClient;SemaphoreHandle_t tcpMutex;// TCP互斥锁QueueHandle_t tcpSendQueue;// TCP发送队列// ⚠️ 函数前向声明voidtcpSendQueuePrint(String msg);// 定义回调函数类型typedefvoid(*TcpMessageCallback)(String message);TcpMessageCallback onTcpMessageReceived=nullptr;// ==================== 用户回调函数 ====================// 当接收到完整的一行 TCP 数据时,会在这里处理voidhandleTcpMessage(String msg){Serial.print("[回调触发] 收到完整指令: ");Serial.println(msg);// 业务逻辑:根据收到的指令控制硬件if(msg=="LED_ON"){// digitalWrite(2, HIGH);tcpSendQueuePrint("LED 已打开");}elseif(msg=="LED_OFF"){// digitalWrite(2, LOW);tcpSendQueuePrint("LED 已关闭");}}// 辅助函数:将字符串放入发送队列voidtcpSendQueuePrint(String msg){if(tcpSendQueue!=NULL){String data=msg+"\n";// 增加换行符,方便接收方解析xQueueSend(tcpSendQueue,&data,portMAX_DELAY);}}// ==================== TCP 接收线程 ====================voidtcpReceiveTask(void*pvParameters){charrxBuffer[512];// 使用字符数组,避免String内存碎片intrxIndex=0;unsignedlonglastRxTime=0;for(;;){if(tcpClient.connected()){if(xSemaphoreTake(tcpMutex,portMAX_DELAY)==pdTRUE){while(tcpClient.available()>0){charc=tcpClient.read();if(rxIndex<sizeof(rxBuffer)-1){rxBuffer[rxIndex++]=c;rxBuffer[rxIndex]='\0';}lastRxTime=millis();// 每次收到字符更新最后接收时间}xSemaphoreGive(tcpMutex);}// 处理缓冲区数据(遇到换行符 或 空闲超过50ms)if(rxIndex>0){boolhasNewline=(strchr(rxBuffer,'\n')!=NULL);// 如果收到了换行符,或者距离最后一次收数据超过了 50msif(hasNewline||(millis()-lastRxTime>50)){// 将 \r 和 \n 替换为字符串结束符,实现 trim 效果for(inti=0;i<rxIndex;i++){if(rxBuffer[i]=='\r'||rxBuffer[i]=='\n'){rxBuffer[i]='\0';}}if(strlen(rxBuffer)>0&&onTcpMessageReceived!=nullptr){onTcpMessageReceived(String(rxBuffer));// 触发回调}rxIndex=0;// 清空缓冲区}}}else{// 断线重连逻辑staticunsignedlonglastReconnect=0;if(millis()-lastReconnect>3000){lastReconnect=millis();Serial.println("\n[线程] TCP 连接已断开,尝试重连...");xSemaphoreTake(tcpMutex,portMAX_DELAY);tcpClient.stop();if(tcpClient.connect(serverIP,serverPort)){tcpClient.setNoDelay(true);Serial.println("[线程] 重连成功!");tcpSendQueuePrint("ESP32-S3 重新上线");}xSemaphoreGive(tcpMutex);}}vTaskDelay(10/portTICK_PERIOD_MS);}}// ==================== TCP 发送线程 ====================voidtcpSendTask(void*pvParameters){String txData;for(;;){if(xQueueReceive(tcpSendQueue,&txData,portMAX_DELAY)==pdTRUE){if(tcpClient.connected()){if(xSemaphoreTake(tcpMutex,portMAX_DELAY)==pdTRUE){tcpClient.print(txData);xSemaphoreGive(tcpMutex);Serial.print("[TCP发送] ");Serial.print(txData);}}}}}// ==================== 初始化函数 ====================voidconnectWiFi(){Serial.print("正在连接 WiFi");WiFi.begin(ssid,password);while(WiFi.status()!=WL_CONNECTED){delay(500);Serial.print(".");}Serial.println("\nWiFi 连接成功");Serial.print("ESP32-S3 本机 IP: ");Serial.println(WiFi.localIP());}boolconnectServer(){Serial.printf("尝试连接 TCP 服务端 %s:%d ...\n",serverIP,serverPort);if(tcpClient.connect(serverIP,serverPort)){tcpClient.setNoDelay(true);Serial.println("连接成功!");returntrue;}returnfalse;}// ==================== 主程序 ====================voidsetup(){Serial.begin(115200);delay(1000);connectWiFi();while(!connectServer()){Serial.println("连接失败,5秒后重试...");delay(5000);}onTcpMessageReceived=handleTcpMessage;// 创建互斥锁和发送队列tcpMutex=xSemaphoreCreateMutex();tcpSendQueue=xQueueCreate(20,sizeof(String));// 创建接收任务(栈空间 8192,防止 String 操作溢出)xTaskCreatePinnedToCore(tcpReceiveTask,"TCP_Recv",8192,NULL,2,NULL,0);// 创建发送任务(栈空间 4096)xTaskCreatePinnedToCore(tcpSendTask,"TCP_Send",4096,NULL,1,NULL,0);tcpSendQueuePrint("ESP32-S3 已上线");}voidloop(){// 主循环只处理串口输入,其他全部交给线程if(Serial.available()>0){String serialData=Serial.readStringUntil('\n');serialData.trim();if(serialData.length()>0){tcpSendQueuePrint(serialData);}}delay(10);}

四、 测试过程与现象

1. PC端(NetAssist)配置

  • 协议类型:TCP Server
  • 本地主机地址:192.168.170.162
  • 本地主机端口:7211
  • 发送设置:勾选“行尾”选择LF,取消“循环周期”。
  • 点击“开始监听”。

2. 测试现象

ESP32 烧录复位后,串口打印ESP32-S3 已上线,NetAssist 显示客户端连接成功(ALL CLIENTS (1))。

  • PC -> ESP32 测试:
    在 NetAssist 发送框输入LED_ON,点击发送。ESP32 串口瞬间打印[回调触发] 收到完整指令: LED_ON。
  • ESP32 -> PC 测试:
    在 ESP32 串口监视器输入Hello Server并回车,NetAssist 数据日志瞬间显示接收到完整的Hello Server。
  • 高频双向压测(3Hz):
    将 NetAssist 发送周期设为 100ms 循环发送长字符串(如截图中的Welcome to NetAssist...)。ESP32 依然能够稳定通过回调解析出完整数据,并在发送队列中正常回应,实现了约 3Hz 的无卡顿双向通讯。

五、 避坑指南与核心解析

1. 为什么会出现“粘包”?怎么解决?

TCP 是面向字节流的协议,没有消息边界。当发送频率快(如 3Hz 以上)时,操作系统底层可能会把多次发送的数据合并成一次接收。
解决方案:代码中的tcpReceiveTask使用了字符数组缓冲区。只有当遇到换行符\n或空闲超过50ms时,才截断为一条完整消息并触发回调。既兼容了标准协议,又容错处理了无换行符的裸数据。

2. 为什么不能直接在线程里用String拼接?

在多线程环境下频繁拼接String极易导致堆栈溢出(Stack Overflow)。代码中使用了char rxBuffer[512]代替,配合xSemaphoreTake和xSemaphoreGive保护WiFiClient,确保线程安全。

3. 后续如何对接阿里云?

本地局域网测试稳定后,只需修改两处即可无缝切换到阿里云:

  1. 将serverIP改为阿里云服务器的公网 IP。
  2. 在阿里云控制台的安全组中,放行对应的 TCP 端口。
    注:如果是对接阿里云 IoT 平台(MQTT协议),则需要更换 PubSubClient 库,而非裸 TCP。

总结:通过 FreeRTOS 的多线程调度与回调机制,ESP32-S3 在局域网 TCP 双向通讯中表现出了极低的延迟和极高的稳定性。这套架构可以直接作为工业级物联网项目的通讯底层模板。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询