12. ESP32-S3 WIFI AP模式TCP通信实战:从服务端到客户端的双向数据收发
ESP32-S3 WIFI AP模式TCP通信实战从服务端到客户端的双向数据收发最近好几个朋友在问用ESP32-S3做智能家居设备或者无线调试工具时怎么让设备之间直接通信不经过路由器这种场景其实挺常见的比如两个设备要直接交换数据或者手机直接连接设备进行配置。今天我就来分享一下ESP32-S3在AP模式下实现TCP通信的完整方案包含服务端和客户端两种模式。咱们这个教程会手把手教你搭建一个完整的通信系统ESP32-S3作为热点AP手机或其他设备连接它然后通过TCP协议进行双向数据收发。我会把代码拆开揉碎了讲保证你跟着做一遍就能完全掌握。1. 项目准备与环境搭建1.1 硬件准备首先需要一块ESP32-S3开发板我用的是ESP32S3R8N8这个型号。这个板子内置了WiFi和蓝牙性能足够我们做网络通信实验。如果你用的是其他ESP32-S3开发板代码也是通用的。1.2 软件环境确保你已经搭建好了ESP-IDF开发环境。我用的是ESP-IDF v5.0版本如果你用的是其他版本大部分代码应该也能兼容但建议尽量用较新的版本。注意如果你还没装ESP-IDF可以去乐鑫官网下载安装或者用VSCode的ESP-IDF插件那个安装起来更方便。1.3 项目结构创建一个新的ESP-IDF项目或者直接在现有的项目里添加我们的代码。整个项目主要包含以下几个部分WiFi AP模式初始化TCP服务端/客户端创建数据收发任务错误处理和重连机制2. WiFi AP模式初始化2.1 什么是AP模式先简单说一下AP模式。AP就是Access Point接入点你可以把它理解成一个无线路由器。在AP模式下ESP32-S3自己创建一个WiFi网络其他设备比如手机、电脑可以连接这个网络。和常见的STA模式设备连接路由器不同AP模式不需要外部路由器设备之间可以直接通信。这在很多物联网场景下很有用比如设备配网、设备间直连通信等。2.2 初始化代码详解咱们先看看怎么初始化WiFi AP。下面这段代码是核心我加了详细注释void wifi_init_softap(void) { // 创建事件组用来同步WiFi连接状态 tcp_event_group xEventGroupCreate(); // 初始化TCP/IP协议栈 ESP_ERROR_CHECK(esp_netif_init()); // 创建默认的事件循环用来处理WiFi事件 ESP_ERROR_CHECK(esp_event_loop_create_default()); // 创建默认的WiFi AP网络接口 esp_netif_create_default_wifi_ap(); // WiFi初始化配置使用默认参数 wifi_init_config_t cfg WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(cfg)); // 注册WiFi事件处理函数 // 当有设备连接或断开时会触发相应的事件 ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL, NULL)); // 配置AP参数 wifi_config_t wifi_config { .ap { .ssid EXAMPLE_ESP_WIFI_SSID, // WiFi名称 .ssid_len strlen(EXAMPLE_ESP_WIFI_SSID), // 名称长度 .password EXAMPLE_ESP_WIFI_PASS, // WiFi密码 .max_connection EXAMPLE_MAX_STA_CONN, // 最大连接数这里设为5 .authmode WIFI_AUTH_WPA2_PSK, // 加密方式WPA2 .pmf_cfg { .required true, // 保护管理帧增强安全性 }, }, }; // 如果没有设置密码就用开放网络 if (strlen(EXAMPLE_ESP_WIFI_PASS) 0) { wifi_config.ap.authmode WIFI_AUTH_OPEN; } // 设置WiFi为AP模式 ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); // 应用AP配置 ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, wifi_config)); // 启动WiFi ESP_ERROR_CHECK(esp_wifi_start()); ESP_LOGI(TAG, wifi_init_softap finished. SSID:%s password:%s, EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); }这里有几个关键点需要注意事件组我们用xEventGroupCreate()创建了一个事件组后面会用它来同步TCP连接。当有设备连接上AP时我们会设置一个事件位当设备断开时清除这个事件位。网络接口初始化esp_netif_init()和esp_event_loop_create_default()是必须的它们初始化了底层的网络协议栈和事件处理机制。AP配置参数SSIDWiFi名称我设为LCKFB-ESP32密码12345678最大连接数5个设备加密方式WPA2这是目前比较安全的加密方式事件处理我们注册了wifi_event_handler函数来处理WiFi事件。这个函数很重要它会在设备连接或断开时被调用。2.3 WiFi事件处理当有设备连接或断开我们的AP时系统会触发事件。我们需要处理这些事件来知道什么时候可以建立TCP连接static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { switch (event_id){ case WIFI_EVENT_AP_STACONNECTED: // 有设备连接成功 // 设置事件位告诉TCP任务可以开始连接了 xEventGroupSetBits(tcp_event_group, WIFI_CONNECTED_BIT); break; case WIFI_EVENT_AP_STADISCONNECTED: // 有设备断开连接 // 标记需要重新建立TCP连接 g_rxtx_need_restart true; // 清除事件位 xEventGroupClearBits(tcp_event_group, WIFI_CONNECTED_BIT); break; default: break; } }这个机制很实用有设备连上时我们设置一个标志位设备断开时我们清除标志位并标记需要重连。这样TCP任务就知道什么时候该建立连接什么时候该重连。3. TCP服务端实现3.1 服务端工作流程服务端的工作流程是这样的创建socket可以理解为一个通信端点绑定到指定的IP和端口监听连接请求接受客户端的连接接收和发送数据3.2 创建TCP服务端下面是创建TCP服务端的核心函数我把它拆开一步步讲esp_err_t create_tcp_server(bool isCreatServer) { // 如果是第一次创建服务器需要创建socket并绑定 if (isCreatServer){ ESP_LOGI(TAG, server socket....,port%d, TCP_PORT); // 创建socketAF_INET表示IPv4SOCK_STREAM表示TCP server_socket socket(AF_INET, SOCK_STREAM, 0); if (server_socket 0){ show_socket_error_reason(create_server, server_socket); close(server_socket); return ESP_FAIL; } // 配置服务器地址信息 server_addr.sin_family AF_INET; // IPv4 server_addr.sin_port htons(TCP_PORT); // 端口号9527htons转换字节序 server_addr.sin_addr.s_addr htonl(INADDR_ANY); // 监听所有网卡 // 绑定socket到地址 if (bind(server_socket, (struct sockaddr *)server_addr, sizeof(server_addr)) 0){ show_socket_error_reason(bind_server, server_socket); close(server_socket); return ESP_FAIL; } } // 开始监听最多5个连接在队列中等待 if (listen(server_socket, 5) 0){ show_socket_error_reason(listen_server, server_socket); close(server_socket); return ESP_FAIL; } // 接受客户端连接 connect_socket accept(server_socket, (struct sockaddr *)client_addr, socklen); if (connect_socket 0){ show_socket_error_reason(accept_server, connect_socket); close(server_socket); return ESP_FAIL; } ESP_LOGI(TAG, tcp connection established!); return ESP_OK; }这里有几个技术细节需要注意socket创建socket(AF_INET, SOCK_STREAM, 0)创建了一个TCP socket。AF_INET表示IPv4SOCK_STREAM表示面向连接的TCP协议。地址绑定bind()函数把socket绑定到指定的IP和端口。INADDR_ANY表示监听所有网卡对于AP模式ESP32-S3的IP默认是192.168.4.1。监听和接受listen()开始监听连接请求参数5表示最多可以有5个连接在队列中等待处理。accept()会阻塞等待直到有客户端连接进来。提示htons()和htonl()是字节序转换函数。网络字节序是大端序而我们的ESP32-S3是小端序所以需要转换。3.3 数据收发任务连接建立后我们需要一个任务来处理数据收发void recv_data(void *pvParameters) { int len 0; char databuff[1024]; // 接收缓冲区1KB大小 while (1){ memset(databuff, 0x00, sizeof(databuff)); // 清空缓冲区 // 接收数据recv是阻塞函数会一直等待直到有数据到来 len recv(connect_socket, databuff, sizeof(databuff), 0); g_rxtx_need_restart false; if (len 0){ ESP_LOGI(TAG, recvData: %s, databuff); // 打印接收到的数据 // 把收到的数据原样发回去回显 send(connect_socket, databuff, strlen(databuff), 0); }else{ // 接收出错打印错误信息 show_socket_error_reason(recv_data, connect_socket); g_rxtx_need_restart true; // 标记需要重连 vTaskDelete(NULL); // 删除当前任务 } } close_socket(); g_rxtx_need_restart true; vTaskDelete(NULL); }这个任务做了几件事创建一个1KB的缓冲区用recv()接收数据这个函数会阻塞等待如果收到数据打印出来并原样发回回显如果接收出错标记需要重连并退出任务3.4 主任务流程TCP连接的主任务负责协调整个流程static void tcp_connect(void *pvParameters) { while (1){ g_rxtx_need_restart false; // 等待WiFi连接事件有设备连上AP才继续 xEventGroupWaitBits(tcp_event_group, WIFI_CONNECTED_BIT, false, true, portMAX_DELAY); ESP_LOGI(TAG, start tcp connected); TaskHandle_t tx_rx_task NULL; vTaskDelay(3000 / portTICK_PERIOD_MS); // 延时3秒给设备一些准备时间 // 创建TCP服务器 ESP_LOGI(TAG, create tcp server); int socket_ret create_tcp_server(true); if (socket_ret ESP_FAIL){ ESP_LOGI(TAG, create tcp socket error,stop...); continue; // 创建失败重试 }else{ ESP_LOGI(TAG, create tcp socket succeed...); // 创建数据接收任务 if (pdPASS ! xTaskCreate(recv_data, recv_data, 4096, NULL, 4, tx_rx_task)){ ESP_LOGI(TAG, Recv task create fail!); }else{ ESP_LOGI(TAG, Recv task create succeed!); } } // 主循环检查是否需要重连 while (1){ vTaskDelay(3000 / portTICK_PERIOD_MS); if (g_rxtx_need_restart){ ESP_LOGI(TAG, tcp server error,some client leave,restart...); // 重新建立连接不是第一次创建所以传false if (ESP_FAIL ! create_tcp_server(false)){ if (pdPASS ! xTaskCreate(recv_data, recv_data, 4096, NULL, 4, tx_rx_task)){ ESP_LOGE(TAG, tcp server Recv task create fail!); }else{ ESP_LOGI(TAG, tcp server Recv task create succeed!); g_rxtx_need_restart false; // 清除重连标记 } } } } } vTaskDelete(NULL); }这个任务的设计思路是等待有设备连接AP创建TCP服务器创建数据接收任务定期检查是否需要重连比如客户端断开4. TCP客户端实现4.1 客户端与服务端的区别客户端和服务端的主要区别在于连接方式服务端绑定端口监听连接接受连接客户端知道服务端的IP和端口主动发起连接在我们的例子里服务端IP是192.168.4.1ESP32-S3 AP的默认IP端口是9527。4.2 创建TCP客户端客户端代码和服务端类似但更简单一些esp_err_t create_tcp_client(void) { ESP_LOGI(TAG, will connect gateway ssid : %s port:%d, TCP_SERVER_ADRESS, TCP_PORT); // 创建socket connect_socket socket(AF_INET, SOCK_STREAM, 0); if (connect_socket 0){ show_socket_error_reason(create client, connect_socket); close(connect_socket); return ESP_FAIL; } // 配置服务器地址信息 server_addr.sin_family AF_INET; server_addr.sin_port htons(TCP_PORT); server_addr.sin_addr.s_addr inet_addr(TCP_SERVER_ADRESS); // 服务端IP ESP_LOGI(TAG, connectting server...); // 连接服务器 if (connect(connect_socket, (struct sockaddr *)server_addr, sizeof(server_addr)) 0){ show_socket_error_reason(client connect, connect_socket); ESP_LOGE(TAG, connect failed!); close(connect_socket); return ESP_FAIL; } ESP_LOGI(TAG, connect success!); return ESP_OK; }客户端的关键步骤创建socket和服务端一样配置要连接的服务端地址IP:192.168.4.2端口:9527调用connect()主动连接服务端注意这里的TCP_SERVER_ADRESS是192.168.4.2这是另一个ESP32-S3作为服务端的IP。在实际项目中你需要根据实际情况修改这个地址。4.3 客户端的主任务客户端的主任务和服务端类似但连接逻辑不同static void tcp_connect(void *pvParameters) { while (1){ g_rxtx_need_restart false; // 等待WiFi连接事件 xEventGroupWaitBits(tcp_event_group, WIFI_CONNECTED_BIT, false, true, portMAX_DELAY); ESP_LOGI(TAG, start tcp connected); TaskHandle_t tx_rx_task NULL; vTaskDelay(3000 / portTICK_PERIOD_MS); // 创建TCP客户端连接 ESP_LOGI(TAG, create tcp server); int socket_ret create_tcp_client(); // 这里是客户端连接 if (socket_ret ESP_FAIL){ ESP_LOGI(TAG, create tcp socket error,stop...); continue; }else{ ESP_LOGI(TAG, create tcp socket succeed...); // 创建数据接收任务和服务器端一样 if (pdPASS ! xTaskCreate(recv_data, recv_data, 4096, NULL, 4, tx_rx_task)){ ESP_LOGI(TAG, Recv task create fail!); }else{ ESP_LOGI(TAG, Recv task create succeed!); } } // 检查重连 while (1){ vTaskDelay(3000 / portTICK_PERIOD_MS); if (g_rxtx_need_restart){ ESP_LOGI(TAG, tcp server error,some client leave,restart...); // 重新连接服务器 if (ESP_FAIL ! create_tcp_client()){ if (pdPASS ! xTaskCreate(recv_data, recv_data, 4096, NULL, 4, tx_rx_task)){ ESP_LOGE(TAG, tcp client Recv task create fail!); }else{ ESP_LOGI(TAG, tcp client Recv task create succeed!); g_rxtx_need_restart false; } } } } } vTaskDelete(NULL); }4.4 按键触发连接客户端代码里还有一个有趣的设计用BOOT按键来触发TCP连接。这个在实际项目中很实用比如设备启动后等待用户按键才开始连接。void app_main(void) { // ... 初始化代码 ... // 配置GPIO0BOOT按键为输入模式 gpio_config_t io_conf; io_conf.intr_type GPIO_INTR_ANYEDGE; // 下降沿和上升沿触发中断 io_conf.pin_bit_mask 1 0; // GPIO0 io_conf.mode GPIO_MODE_INPUT; // 输入模式 io_conf.pull_up_en GPIO_PULLUP_ENABLE; // 使能上拉 gpio_config(io_conf); while(1) { vTaskDelay(500/portTICK_PERIOD_MS); if(gpio_get_level(0)0){ // 检测按键按下低电平 // 创建TCP连接任务 xTaskCreate(tcp_connect, tcp_connect, 4096, NULL, 5, NULL); break; } } }这样设计的好处是设备启动后先创建AP然后等待用户按下BOOT键才开始连接服务器。这在调试和测试时很方便。5. 测试与验证5.1 服务端测试编译并下载服务端代码到ESP32-S3手机搜索WiFiLCKFB-ESP32密码12345678手机连接这个WiFi在手机上打开TCP调试助手比如网络调试助手APP设置为TCP客户端IP填192.168.4.1端口9527点击连接应该能看到连接成功的日志发送数据ESP32-S3会回显同样的数据5.2 客户端测试先确保有一个ESP32-S3运行服务端代码编译并下载客户端代码到另一个ESP32-S3手机连接客户端ESP32-S3的AP同样是LCKFB-ESP32在手机上打开TCP调试助手设置为TCP服务器端口9527按下客户端ESP32-S3的BOOT键客户端会连接服务端192.168.4.2:9527在手机端发送数据客户端收到后会回显5.3 常见问题排查在实际测试中可能会遇到一些问题这里分享几个我踩过的坑问题1连接失败检查IP地址是否正确服务端是192.168.4.1客户端要连接192.168.4.2检查端口号两边都要是9527检查防火墙有些手机会阻止网络调试助手的网络访问问题2数据收发不正常检查缓冲区大小我们的代码用了1024字节缓冲区如果发送的数据超过这个大小需要分多次发送检查字符串结束符recv()接收的是原始数据如果当字符串处理要确保有\0结束符问题3连接不稳定调整重连机制代码里已经有重连逻辑但你可以根据实际情况调整重连间隔检查信号强度AP模式信号范围有限确保设备在有效范围内6. 实际项目应用建议这个代码框架在实际项目中可以直接用但可能需要根据具体需求做一些调整1. 数据协议设计现在的代码是简单的回显实际项目需要定义通信协议。比如可以这样设计// 定义数据包结构 typedef struct { uint8_t header[2]; // 包头比如0xAA 0x55 uint16_t length; // 数据长度 uint8_t cmd; // 命令字 uint8_t data[256]; // 数据内容 uint8_t checksum; // 校验和 } data_packet_t;2. 多客户端支持当前服务端只支持一个客户端连接。如果需要支持多个客户端可以用select()或poll()实现多路复用或者为每个客户端创建一个独立的任务。3. 错误恢复增强代码里已经有基本的错误恢复机制但可以进一步增强添加心跳包检测连接状态实现断线自动重连添加日志记录方便排查问题4. 安全性考虑实际项目中不要用固定密码12345678可以考虑添加数据加密实现身份验证机制这个AP模式的TCP通信框架我在好几个项目里都用过比如无线调试工具、设备间直接数据同步、智能家居设备配网等场景。掌握了这个基础你就能根据具体需求进行扩展和优化了。代码里我尽量加了详细的注释但嵌入式开发就是这样看着简单调试起来可能会遇到各种问题。如果测试中遇到问题可以先检查日志输出ESP32的日志系统很强大能帮你快速定位问题所在。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2409060.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!