You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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参数调整最大连接数。

注意事项

  1. 确保WiFi SSID和密码通过idf.py menuconfig正确配置。
  2. UART引脚9和10属于ESP32的SPI Flash复用引脚,若使用外部Flash可能存在冲突,建议更换为UART0(引脚1、3)或UART2(引脚16、17)。

内容的提问来源于stack exchange,提问作者shaiknisar_powerfrill

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 05:39:52