ESP32(ESP-IDF)网页服务器接收客户端数据6次后中断求助
问题:ESP32网页服务器接收6次客户端数据后停止响应
使用VS Code搭配ESP-IDF扩展开发ESP32网页服务器,通过网页滑块发送转速数据控制电机,未使用Platform-IO。但ESP32在接收6次客户端数据后便停止响应,附上代码求问题原因及替代实现方案。
问题原因分析
- HTTP连接资源耗尽:ESP-IDF HTTP服务器默认最大并发连接数为6,在
ReceiveSpeedFrom_Webclient_handler中仅返回ESP_OK但未向客户端发送响应,导致每个请求的连接都处于挂起状态。当6个连接全部占用后,服务器无法处理新请求,直接停止响应。 - 潜在内存越界风险:注释掉的UART处理代码中,直接从URI固定位置截取数据并写入固定大小的数组,若参数长度超出数组限制会引发内存越界,可能导致系统崩溃(当前代码中该部分被注释,不是本次故障的直接原因)。
修复后的ESP-IDF实现方案
1. 核心修复逻辑
确保每个HTTP请求都能得到响应,释放连接资源;安全解析URL参数,避免直接操作URI字符串;规范UART数据处理,防止内存越界。
2. 完整代码实现
#include <stdio.h> #include <string.h> #include <stdlib.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_system.h" #include "esp_wifi.h" #include "esp_event.h" #include "esp_log.h" #include "esp_netif.h" #include "nvs_flash.h" #include "driver/gpio.h" #include "driver/uart.h" #include "lwip/err.h" #include "lwip/sys.h" #include <lwip/sockets.h> #include <lwip/sys.h> #include <lwip/api.h> #include <lwip/netdb.h> #include "spi_flash_mmap.h" #include <esp_http_server.h> /* UART引脚定义 */ #define UART_1_TX_PIN (10) #define UART_1_RX_PIN (9) static const int UART_RX_BUF_SIZE = 1024; /* WiFi配置(通过idf.py menuconfig设置) */ #define ESP_HOST_WIFI_SSID CONFIG_ESP_WIFI_SSID #define ESP_HOST_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD #define ESP_HOST_MAXIMUM_RETRY CONFIG_ESP_MAXIMUM_RETRY /* FreeRTOS事件组 */ static EventGroupHandle_t s_wifi_event_group; #define WIFI_CONNECTED_BIT BIT0 #define WIFI_FAIL_BIT BIT1 static const char *TAG = "ESP Web-Server"; static int s_retry_num = 0; /* WiFi事件处理函数 */ static void 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_START) { esp_wifi_connect(); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { if (s_retry_num < ESP_HOST_MAXIMUM_RETRY) { esp_wifi_connect(); s_retry_num++; ESP_LOGI(TAG, "retry to connect to the AP"); } else { xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); } ESP_LOGI(TAG,"connect to the AP fail"); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); s_retry_num = 0; xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } } /* WiFi STA模式初始化 */ void WiFi_InitIn_StationMode(void) { s_wifi_event_group = xEventGroupCreate(); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); esp_event_handler_instance_t instance_any_id; esp_event_handler_instance_t instance_got_ip; ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip)); wifi_config_t wifi_config = { .sta = { .ssid = ESP_HOST_WIFI_SSID, .password = ESP_HOST_WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK, .sae_pwe_h2e = WPA3_SAE_PWE_BOTH, .sae_h2e_identifier = "", }, }; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); ESP_ERROR_CHECK(esp_wifi_start() ); ESP_LOGI(TAG, "wifi_init_sta finished."); EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY); if (bits & WIFI_CONNECTED_BIT) { ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", ESP_HOST_WIFI_SSID, ESP_HOST_WIFI_PASS); } else if (bits & WIFI_FAIL_BIT) { ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", ESP_HOST_WIFI_SSID, ESP_HOST_WIFI_PASS); } else { ESP_LOGE(TAG, "UNEXPECTED EVENT"); } } /* UART初始化 */ void UART_Init(void) { const uart_config_t uart_config = { .baud_rate = 9600, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_DEFAULT, }; uart_driver_install(UART_NUM_1, UART_RX_BUF_SIZE * 2, 0, 0, NULL, 0); uart_param_config(UART_NUM_1, &uart_config); uart_set_pin(UART_NUM_1, UART_1_TX_PIN, UART_1_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); } /* 发送网页 */ esp_err_t send_web_page(httpd_req_t *req) { char web_homepage_string[] = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><style>html {font-family: Arial; display: inline-block; text-align: center;}h1 {font-size: 2.2rem;color:green;}h2 {font-size: 2.1rem;}p {font-size: 1.9rem;} body {max-width: 500px; margin:0px auto; padding-bottom: 30px;} .slider { -webkit-appearance: none; margin: 14px; width: 400px; height: 15px; border-radius: 5px; background: #39a6de; outline: none; -webkit-transition: .2s; transition: opacity .2s;} .slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 25px; height: 25px; border-radius: 12px; background: #f74d4d; cursor: pointer;} .slider::-moz-range-thumb { width: 25px; height: 25px; border-radius: 12px; background: #F74D4D; cursor: pointer; } </style> </head> <body> <h1>Rhino Motor Speed Control</h1><h2>Speed Value</h2> <p><span id=\"textSliderValue\">0</span></p> <p><input type=\"range\" onchange=\"updateSliderVal(this)\" id=\"speedSlider\" min=\"-255\" max=\"255\" value=\"0\" step=\"5\" class=\"slider\"></p> <script> function updateSliderVal(element) { var motorSliderValue = document.getElementById(\"speedSlider\").value; document.getElementById(\"textSliderValue\").innerHTML = motorSliderValue; console.log(motorSliderValue); var httpRequest = new XMLHttpRequest(); httpRequest.open(\"GET\", \"/webclient_speed?value=\" + motorSliderValue, true); httpRequest.send();}</script> </body> </html>"; return httpd_resp_send(req, web_homepage_string, HTTPD_RESP_USE_STRLEN); } /* 首页请求处理 */ esp_err_t HomePage_req_handler(httpd_req_t *req) { return send_web_page(req); } /* 转速数据接收处理 */ esp_err_t ReceiveSpeedFrom_Webclient_handler(httpd_req_t *req) { // 解析URL参数中的转速值 char param_buf[16] = {0}; if (httpd_req_get_url_query_str(req, param_buf, sizeof(param_buf)) == ESP_OK) { char speed_str[8] = {0}; if (httpd_query_key_value(param_buf, "value", speed_str, sizeof(speed_str)) == ESP_OK) { long speed = atol(speed_str); ESP_LOGI(TAG, "Motor speed received: %ld", speed); // 安全格式化UART发送数据 char uart_buf[16] = {0}; snprintf(uart_buf, sizeof(uart_buf), "S%ld\n", speed); uart_write_bytes(UART_NUM_1, uart_buf, strlen(uart_buf)); } } // 必须发送响应,释放HTTP连接资源 const char* resp_str = "OK"; httpd_resp_send(req, resp_str, strlen(resp_str)); return ESP_OK; } /* HTTP URI处理器定义 */ httpd_uri_t uri_get_homepage = { .uri = "/", .method = HTTP_GET, .handler = HomePage_req_handler, .user_ctx = NULL }; httpd_uri_t GetSpeedValue_FromWebClient = { .uri = "/webclient_speed", .method = HTTP_GET, .handler = ReceiveSpeedFrom_Webclient_handler, .user_ctx = NULL }; /* 启动HTTP服务器 */ httpd_handle_t Setup_HTTP_server(void) { httpd_config_t HTTP_ServerConfig = HTTPD_DEFAULT_CONFIG(); // 可选:调整最大并发连接数,默认是6 // HTTP_ServerConfig.max_open_sockets = 10; httpd_handle_t HTTP_ServerDetails = NULL; if (httpd_start(&HTTP_ServerDetails, &HTTP_ServerConfig) == ESP_OK) { httpd_register_uri_handler(HTTP_ServerDetails, &uri_get_homepage); httpd_register_uri_handler(HTTP_ServerDetails, &GetSpeedValue_FromWebClient); ESP_LOGI(TAG, "HTTP server started successfully"); } else { ESP_LOGE(TAG, "Failed to start HTTP server"); } return HTTP_ServerDetails; } /* 主函数 */ void app_main(void) { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); UART_Init(); ESP_LOGI(TAG, "ESP_WIFI_MODE_STA"); WiFi_InitIn_StationMode(); ESP_LOGI(TAG, "Rhino Motor Control Web Server is running ..."); Setup_HTTP_server(); }
3. 关键优化点
- 强制响应HTTP请求:在每个请求处理函数中调用
httpd_resp_send,确保连接资源及时释放,避免连接耗尽。 - 安全解析参数:使用ESP-IDF提供的
httpd_req_get_url_query_str和httpd_query_key_value函数解析URL参数,替代直接操作URI字符串,避免索引越界。 - 规范UART数据处理:用
snprintf格式化UART发送内容,确保不会超出数组长度。 - 可扩展连接数:若需要支持更多并发请求,可修改
HTTP_ServerConfig.max_open_sockets参数调整最大连接数。
注意事项
- 确保WiFi SSID和密码通过
idf.py menuconfig正确配置。 - UART引脚9和10属于ESP32的SPI Flash复用引脚,若使用外部Flash可能存在冲突,建议更换为UART0(引脚1、3)或UART2(引脚16、17)。
内容的提问来源于stack exchange,提问作者shaiknisar_powerfrill
相关产品推荐
相关产品推荐

