恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

《WiFi 嵌入式物联网开发全套实战》| 第 18 章 ESP32 WiFi + TCP/UDP 无线通信实战(衔接 Socket 专栏)

  • 首页
  • 资讯中心
  • /
  • 《WiFi 嵌入式物联网开发全套实战》| 第 18 章 ESP32 WiFi + TCP/UDP 无线通信实战(衔接 Socket 专栏)

相关资讯

不造整机,照样吃肉:揭秘人形机器人“卖铲人”的5大黄金标的 2026/9/24 17:13:49
423道 GIT 测试题(含解释) 381 - 400 题 2026/9/24 17:13:49
员工信息二次修改,审批人要看到前后差异 2026/9/24 17:13:49

最新资讯

厂房焊接车间智能照明改造:照明节能控制系统人体感应方案
django-allauth 集成 Kakao 登录:OAuth2 配置指南与源码解析
Flink+Hudi实时数据湖入湖实践:从环境搭建到性能调优
MySQL GROUP_CONCAT详解:从多行拼接到性能避坑指南
吊车近电预警装置全解析:原理、安装与维护指南
Python+OpenCV答题卡识别实战:透视校正与填涂判定

今日推荐

JavaWeb购物车系统实现:基于Session存储的完整工程示例
面向对象综合训练:从图书管理系统掌握封装、继承与多态
Lombok与JDK版本冲突引发NoSuchFieldError:根因排查与修复指南

本周热门

BrewUI:给Homebrew套上图形界面,让macOS软件包管理更简单
BrewUI:让Homebrew包管理变得可视化与高效
公式与文本对齐全攻略:从Word到LaTeX的实用技巧

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

《WiFi 嵌入式物联网开发全套实战》| 第 18 章 ESP32 WiFi + TCP/UDP 无线通信实战(衔接 Socket 专栏)

发布时间:2026/9/24 17:13:49
《WiFi 嵌入式物联网开发全套实战》| 第 18 章 ESP32 WiFi + TCP/UDP 无线通信实战(衔接 Socket 专栏) 专栏《WiFi 嵌入式物联网开发全套实战》 专栏定位嵌入式 Linux/ESP32 WiFi 从原理→驱动→配网→协议→稳定性→抓包调试→量产优化全套工业实战适配物联网设备、智能家居、工控网关、无线透传设备、4GWiFi 双模设备 点赞 收藏 关注嵌入式 WiFi 量产实战全套教程持续更新本章前言完成 WiFi 配网与 STA 稳定联网之后设备最终要完成数据收发。ESP32 所有网络业务局域网透传、远程控制、MQTT、HTTP、OTA底层全部依赖 LWIP 的 POSIX Socket。网上绝大多数 Demo 只演示简单阻塞收发调试阶段正常一旦进入长期量产暴露出大量问题任务阻塞卡死、socket 文件描述符泄漏、TCP 假死不断开、粘包分包、WiFi 重连之后 socket 不重建、UDP 广播发送失败、返回值不做判断导致程序崩溃。本章完整覆盖Socket 系统调用详解、TCP 客户端、TCP 服务端、UDP 收发、非阻塞 socket、setsockopt 全套参数、TCP keep‑alive、广播使能、返回值解析、抓包定位问题、量产真实故障现象。代码基于 ESP‑IDF5.x可直接编译继承第 14 章 STA 掉线重连框架。前置阅读第 14 章 ESP32 STA 模式连接路由、自动重连、掉线恢复完整源码。18.1 Socket 在 ESP‑IDF (LWIP) 基础概念Socket 本质是文件描述符 fd类似打开文件得到一个 int 句柄。 LWIP 是 ESP32 内置轻量级 TCP/IP 协议栈兼容标准 POSIX socket APIsocket()、bind()、listen()、accept()、connect()、send()、recv()、sendto()、recvfrom()、close()。工程铁律socket 创建成功一定要 close出现异常、断线、WiFi 断开必须 close否则 fd 泄漏耗尽系统 socket 资源后期网络全部失效只能重启。阻塞 socket 极易卡死 FreeRTOS 任务量产业务一律使用非阻塞 socket。18.1.1 关键函数简要说明函数作用socket()创建套接字返回 fd-1 代表失败connect()TCP 客户端建立连接bind()服务端 / UDP 绑定本地端口listen()TCP 服务端开启监听accept()TCP 服务端接受客户端接入send()TCP 发送数据recv()TCP 接收数据sendto()UDP 发送指定对端地址recvfrom()UDP 接收同时获取发送方 IP 端口close()关闭套接字释放 fd 资源fcntl()设置阻塞 / 非阻塞模式setsockopt()设置超时、keepalive、广播、缓冲区等参数18.1.2 socket 返回值通用解析0成功返回字节数或者有效 fd0TCP recv 返回 0 →对端正常关闭连接-1出错errno记录错误码EAGAIN / EWOULDBLOCK非阻塞模式当前无数据不是错误ECONNRESET对端重置连接ETIMEDOUT连接超时EINTR系统调用被信号中断。量产代码必须判断返回值不能忽略否则会出现野 fd 读写程序崩溃。18.2 setsockopt 量产核心参数详解这是很多 Demo 缺失的部分直接决定 7×24 运行稳定性。18.2.1 SO_RCVTIMEO / SO_SNDTIMEO 读写超时设置接收、发送超时时间结构体struct timeval {tv_sec, tv_usec}。仅对阻塞 socket 生效非阻塞 socket 该选项无效依靠返回值 EAGAIN 判断。18.2.2 SO_KEEPALIVE TCP 内核保活解决静默假死链路WiFi 显示已连接但是对方已经异常断电断网没有 FIN 报文TCP 连接僵死。SO_KEEPALIVE开启保活开关TCP_KEEPIDLE链路空闲多久开始发送保活探测包单位 sTCP_KEEPINTVL探测包之间间隔TCP_KEEPCNT探测失败多少次判定连接断开keep‑alive 属于传输层保活业务层依旧建议自己上层心跳双重保险。18.2.3 SO_BROADCAST UDP 广播开关UDP 默认禁止发送广播报文发送广播包必须开启该选项否则 sendto 返回失败。18.2.4 SO_REUSEADDR 地址复用服务端重启之后端口处于 TIME_WAIT 状态无法立刻 bind开启该选项允许快速复用端口嵌入式服务端建议开启。18.2.5 O_NONBLOCK 非阻塞模式通过fcntl(fd,F_SETFL,flags | O_NONBLOCK)设置。 recv/send/connect 不会阻塞任务没有数据直接返回‑1errnoEAGAIN。18.3 实战 1TCP 客户端完整工程非阻塞 keepalive业务场景ESP32 作为 TCP 客户端主动连接局域网服务器断线自动重建资源安全释放。#include stdio.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include netinet/tcp.h #include arpa/inet.h #include fcntl.h #include errno.h #include freertos/FreeRTOS.h #include freertos/task.h #include freertos/event_groups.h #include esp_wifi.h #include esp_event.h #include esp_log.h #include nvs_flash.h #include esp_netif.h #define WIFI_SSID YourSSID #define WIFI_PASSWORD YourPassword #define TCP_SERVER_IP 192.168.1.100 #define TCP_SERVER_PORT 8080 #define WIFI_CONNECTED_BIT BIT0 static EventGroupHandle_t wifi_event_group; static const char *TAG TCP_CLIENT; static int g_tcp_client_fd -1; static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (event_base WIFI_EVENT event_id WIFI_EVENT_STA_DISCONNECTED) { ESP_LOGW(TAG,STA断开进行重连); esp_wifi_connect(); xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); } else if (event_base IP_EVENT event_id IP_EVENT_STA_GOT_IP) { ESP_LOGI(TAG,获取IP网络就绪); xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); } } /* 安全关闭tcp客户端释放fd防止句柄泄漏 */ static void tcp_client_close(void) { if(g_tcp_client_fd 0) { close(g_tcp_client_fd); g_tcp_client_fd -1; ESP_LOGI(TAG,tcp client socket closed); } } static int tcp_client_connect(void) { int fd socket(AF_INET, SOCK_STREAM, 0); if(fd 0) { ESP_LOGE(TAG,socket create fail, errno%d,errno); return -1; } /* 设置非阻塞 */ int flags fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); /* TCP keep‑alive配置 */ int keepalive_en 1; int keepidle 5; int keepintvl 2; int keepcnt 3; setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, keepalive_en, sizeof(keepalive_en)); setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, keepidle, sizeof(keepidle)); setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, keepintvl, sizeof(keepintvl)); setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, keepcnt, sizeof(keepcnt)); struct sockaddr_in serv_addr {0}; serv_addr.sin_family AF_INET; serv_addr.sin_port htons(TCP_SERVER_PORT); inet_pton(AF_INET, TCP_SERVER_IP, serv_addr.sin_addr); int ret connect(fd, (struct sockaddr *)serv_addr, sizeof(serv_addr)); if(ret 0) { /* 非阻塞connect返回‑1EINPROGRESS表示正在后台连接不算失败 */ if(errno ! EINPROGRESS) { ESP_LOGE(TAG,connect fail errno%d,errno); close(fd); return -1; } } ESP_LOGI(TAG,tcp client connect initiating); return fd; } static void tcp_client_task(void *arg) { char rx_buf[1024] {0}; while(1) { /* 等待WiFi联网 */ xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY); if(g_tcp_client_fd 0) { g_tcp_client_fd tcp_client_connect(); vTaskDelay(pdMS_TO_TICKS(1200)); continue; } int rlen recv(g_tcp_client_fd, rx_buf, sizeof(rx_buf)-1, 0); if(rlen 0) { rx_buf[rlen] \0; ESP_LOGI(TAG,recv:%s, rx_buf); /* 回显测试 */ send(g_tcp_client_fd, rx_buf, rlen, 0); } else if(rlen 0) { ESP_LOGW(TAG,对端关闭连接); tcp_client_close(); } else if(rlen -1) { if(errno ! EAGAIN errno ! EWOULDBLOCK) { ESP_LOGW(TAG,recv error errno:%d,errno); tcp_client_close(); } } vTaskDelay(pdMS_TO_TICKS(100)); } } static void wifi_sta_init(void) { wifi_event_group xEventGroupCreate(); esp_netif_init(); esp_event_loop_create_default(); esp_netif_create_default_wifi_sta(); wifi_init_config_t wcfg WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(wcfg); esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL, NULL); esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL, NULL); wifi_config_t sta_cfg { .sta { .ssid WIFI_SSID, .password WIFI_PASSWORD, } }; esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_set_config(WIFI_IF_STA, sta_cfg); esp_wifi_start(); esp_wifi_connect(); } void app_main(void) { nvs_flash_init(); wifi_sta_init(); xTaskCreate(tcp_client_task, tcp_client, 4096, NULL, 5, NULL); }18.3.1 代码重点解读非阻塞 connect 会返回‑1errno EINPROGRESS代表正在建立连接不要直接关闭 socketrecv 返回‑1区分EAGAIN/EWOULDBLOCK无数据正常和真实错误对端关闭连接 recv 返回 0必须立刻 closeWiFi 断开时上层业务需要主动调用tcp_client_close()旧 fd 失效不能复用。18.4 实战 2TCP 服务端ESP32 作为服务端接收多个客户端注意ESP32 资源有限不适合大量并发一般局域网设备控制 2‑4 个客户端上限。static int g_tcp_server_fd -1; static void tcp_server_close(void) { if(g_tcp_server_fd 0){ close(g_tcp_server_fd); g_tcp_server_fd -1; } } static int tcp_server_init(uint16_t port) { int fd socket(AF_INET, SOCK_STREAM,0); if(fd 0) return -1; int reuse 1; setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,reuse,sizeof(reuse)); struct sockaddr_in saddr {0}; saddr.sin_family AF_INET; saddr.sin_port htons(port); saddr.sin_addr.s_addr htonl(INADDR_ANY); if(bind(fd,(struct sockaddr*)saddr,sizeof(saddr)) 0) { close(fd); return -1; } listen(fd,4); /* 设置非阻塞 */ int flags fcntl(fd,F_GETFL,0); fcntl(fd,F_SETFL,flags|O_NONBLOCK); return fd; } static void tcp_server_task(void *arg) { int client_fd -1; char buf[256]; struct sockaddr_in c_addr; socklen_t c_len sizeof(c_addr); while(1) { xEventGroupWaitBits(wifi_event_group,WIFI_CONNECTED_BIT,pdFALSE,pdFALSE,portMAX_DELAY); if(g_tcp_server_fd 0) { g_tcp_server_fd tcp_server_init(8080); vTaskDelay(pdMS_TO_TICKS(1000)); continue; } client_fd accept(g_tcp_server_fd, (struct sockaddr *)c_addr, c_len); if(client_fd 0) { ESP_LOGI(TAG,new client connect); /* 此处真实项目新建子任务处理该client_fd注意资源管理 */ } else if(client_fd -1) { if(errno ! EAGAIN errno ! EWOULDBLOCK) { tcp_server_close(); } } vTaskDelay(pdMS_TO_TICKS(50)); } }量产提醒accept 拿到客户端 fd 之后一定要单独任务处理收发不要在 accept 循环内部阻塞每个客户端断开必须 close 对应 client_fd否则 fd 泄漏。18.5 实战 3UDP 收发 广播发送完整代码UDP 无连接用于局域网设备发现、广播心跳。重点SO_BROADCAST开启广播。static int g_udp_fd -1; static void udp_close(void) { if(g_udp_fd 0){ close(g_udp_fd); g_udp_fd -1; } } static int udp_init(uint16_t local_port) { int fd socket(AF_INET, SOCK_DGRAM,0); if(fd 0) return -1; int broadcast_en 1; setsockopt(fd,SOL_SOCKET,SO_BROADCAST,broadcast_en,sizeof(broadcast_en)); struct sockaddr_in laddr {0}; laddr.sin_family AF_INET; laddr.sin_port htons(local_port); laddr.sin_addr.s_addr htonl(INADDR_ANY); bind(fd,(struct sockaddr*)laddr,sizeof(laddr)); int flags fcntl(fd,F_GETFL,0); fcntl(fd,F_SETFL,flags|O_NONBLOCK); return fd; } static void udp_task(void *arg) { char rx_buf[256]; char tx_buf[] ESP32 UDP BROADCAST; struct sockaddr_in dst_addr; socklen_t dst_len sizeof(dst_addr); memset(dst_addr,0,dst_len); dst_addr.sin_family AF_INET; dst_addr.sin_port htons(8888); inet_pton(AF_INET,192.168.1.255,dst_addr.sin_addr); while(1) { xEventGroupWaitBits(wifi_event_group,WIFI_CONNECTED_BIT,pdFALSE,pdFALSE,portMAX_DELAY); if(g_udp_fd 0) { g_udp_fd udp_init(8888); vTaskDelay(pdMS_TO_TICKS(1000)); continue; } socklen_t from_len sizeof(struct sockaddr_in); int rlen recvfrom(g_udp_fd, rx_buf, sizeof(rx_buf)-1, 0, (struct sockaddr *)dst_addr, from_len); if(rlen0) { rx_buf[rlen]0; ESP_LOGI(TAG,udp recv:%s,rx_buf); } sendto(g_udp_fd, tx_buf, strlen(tx_buf), 0, (struct sockaddr *)dst_addr, dst_len); vTaskDelay(pdMS_TO_TICKS(1000)); } }18.6 抓包定位 Socket 问题WiresharkTCP 连接失败看是否有 SYN 包有 SYN 无 SYN‑ACK服务器未监听、防火墙拦截TCP 假死抓包看不到 keep‑alive 探测包 → setsockopt 参数不生效UDP 广播收不到确认目的地址是网段广播地址检查是否开启SO_BROADCAST路由器 AP 隔离会直接丢弃广播包recv 返回 0抓包看到 FIN 报文对端正常关闭连接recv 返回‑1 ECONNRESET抓包看到 RST 复位包对方强制重置连接。ESP32 自身 log 无法抓空中报文PC 网卡开监听模式抓包。18.7 量产高频故障现象、根因与解决方案故障 1长时间运行网络异常重启恢复现象运行几天之后 connect () 返回失败无法创建 socket根因异常分支没有 close socketfd 泄漏耗尽 LWIP 最大 socket 数量解决所有错误、断开、WiFi 下线全部执行 closefd 置‑1。故障 2WiFi 重连成功但是 TCP 通信不通现象拿到 IPping 正常TCP 完全无响应根因WiFi 底层链路重建旧 socket fd 已经失效代码继续使用旧 fd解决WiFi 断开事件触发关闭全部 socket联网之后重新创建 socket。故障 3TCP 链路静默假死无任何报错日志现象双方无数据交互WiFi 显示正常但是收发全部卡死根因路由器断电没有发送 FIN/RST 报文TCP 状态机认为连接还存在解决开启 TCP keep‑alive上层增加业务心跳报文双重检测。故障 4TCP 粘包分包解析数据错乱现象recv 一次拿到多条报文或者一条报文被分成多次接收根因TCP 是流式协议没有数据包边界解决应用层协议增加帧头、数据长度字段做分包解析不要按 recv 次数当做数据包。故障 5UDP 发送广播失败 sendto 返回‑1根因未设置SO_BROADCAST目的不是广播地址路由器 AP 隔离。解决开启广播选项正确使用网段广播地址产品文档提示关闭 AP 隔离。故障 6阻塞 socket 导致 FreeRTOS 任务卡死整机看似死机根因网络异常recv/connect 永久阻塞任务解决量产业务 socket 全部设置非阻塞模式。18.8 ESP‑IDF menuconfig LWIP 关键调优量产必改Component config → LWIPMax number of open sockets调至 16~20多 socket 场景防止 fd 耗尽TCP send/receive buffer size根据业务报文大小调整关闭 LWIP debug 日志减少串口开销。18.9 本章小结Socket 是 ESP32 网络编程底层基石MQTT、HTTP 等上层协议全部基于 Socket 封装。本章完整讲解 POSIX socket 系统调用、setsockopt 关键参数、非阻塞、keep‑alive 保活、广播配置给出 TCP 客户端、TCP 服务端、UDP 收发三套可编译工程解析返回错误码、抓包调试方法梳理量产真实故障现象与修复方案。量产开发必须牢记三条准则socket 异常必须 close 释放 fd业务优先非阻塞 socket网络重连必须重建 socket不能复用旧句柄。 点赞 收藏 关注嵌入式 WiFi 量产实战全套教程持续更新下一章第 19 章 ESP32 WiFi MQTT 物联网上云完整工程

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号