espidf的esp32版的webClient

参考

espidf的esp32版的webServer测试.csdn

esp32-idf环境安装配置.csdn

HttpClient.hpp

c 复制代码
#ifndef HTTP_CLIENT_HPP
#define HTTP_CLIENT_HPP

#include <string>
#include <cstring>
#include "esp_log.h"
#include "esp_http_client.h"

class HttpClient {
private:
    static const char* TAG;
    static constexpr int DEFAULT_TIMEOUT_MS = 5000;

    // 回调上下文:用于在回调中传递数据
    struct CallbackContext {
        std::string* response;
    };

    // 事件回调函数:在请求过程中实时接收响应数据
    static esp_err_t httpEventHandler(esp_http_client_event_t *evt) {
        CallbackContext* ctx = static_cast<CallbackContext*>(evt->user_data);
        if (ctx == nullptr || ctx->response == nullptr) {
            return ESP_OK;
        }

        switch (evt->event_id) {
            case HTTP_EVENT_ON_DATA:
                if (evt->data_len > 0 && evt->data != nullptr) {
                    ctx->response->append(static_cast<char*>(evt->data), evt->data_len);
                }
                break;
            default:
                break;
        }
        return ESP_OK;
    }

public:
    // GET 请求
    static std::string get(const std::string& url, int timeout_ms = DEFAULT_TIMEOUT_MS) {
        std::string response = "";
        CallbackContext ctx = { &response };

        esp_http_client_config_t config = {};
        config.url = url.c_str();
        config.method = HTTP_METHOD_GET;
        config.timeout_ms = timeout_ms;
        config.keep_alive_enable = false;
        config.event_handler = httpEventHandler;
        config.user_data = &ctx;

        esp_http_client_handle_t client = esp_http_client_init(&config);
        if (client == NULL) {
            ESP_LOGE(TAG, "Failed to initialize HTTP client");
            return "";
        }

        ESP_LOGI(TAG, "GET %s", url.c_str());
        esp_err_t err = esp_http_client_perform(client);

        if (err == ESP_OK) {
            int status_code = esp_http_client_get_status_code(client);
            ESP_LOGI(TAG, "HTTP Status = %d", status_code);
        } else {
            ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
        }

        esp_http_client_cleanup(client);
        return response;
    }

    // POST 请求 (JSON)
    static std::string post(const std::string& url, const std::string& data, int timeout_ms = DEFAULT_TIMEOUT_MS) {
        std::string response = "";
        CallbackContext ctx = { &response };

        esp_http_client_config_t config = {};
        config.url = url.c_str();
        config.method = HTTP_METHOD_POST;
        config.timeout_ms = timeout_ms;
        config.keep_alive_enable = false;
        config.event_handler = httpEventHandler;
        config.user_data = &ctx;

        esp_http_client_handle_t client = esp_http_client_init(&config);
        if (client == NULL) {
            ESP_LOGE(TAG, "Failed to initialize HTTP client");
            return "";
        }

        esp_http_client_set_header(client, "Content-Type", "application/json");
        esp_http_client_set_post_field(client, data.c_str(), data.length());

        ESP_LOGI(TAG, "POST %s", url.c_str());
        esp_err_t err = esp_http_client_perform(client);

        if (err == ESP_OK) {
            int status_code = esp_http_client_get_status_code(client);
            ESP_LOGI(TAG, "HTTP Status = %d", status_code);
        } else {
            ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
        }

        esp_http_client_cleanup(client);
        return response;
    }
};

const char* HttpClient::TAG = "HttpClient";

#endif // HTTP_CLIENT_HPP

main.cpp

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "app_wifi.h"
#include "HttpClient.hpp"

static const char *TAG = "HTTP_TEST";
static const char* SERVER_IP = "192.168.3.38";
static const int SERVER_PORT = 8888;

static void test_http_client(void)
{
    while (1) {
        // --- 测试 GET ---
        char getUrl[128];
        snprintf(getUrl, sizeof(getUrl), "http://%s:%d/apiGet", SERVER_IP, SERVER_PORT);

        TickType_t startGet = xTaskGetTickCount();
        std::string getResp = HttpClient::get(getUrl);
        TickType_t costGet = (xTaskGetTickCount() - startGet) * portTICK_PERIOD_MS;

        if (!getResp.empty()) {
            ESP_LOGI(TAG, "GET Response (%lu ms):\n%s", (unsigned long)costGet, getResp.c_str());
        } else {
            ESP_LOGW(TAG, "GET request returned empty response (cost %lu ms)", (unsigned long)costGet);
        }

        vTaskDelay(pdMS_TO_TICKS(2000));

        // --- 测试 POST ---
        char postUrl[128];
        snprintf(postUrl, sizeof(postUrl), "http://%s:%d/apiPost", SERVER_IP, SERVER_PORT);

        std::string jsonData = "{\"device\":\"ESP32\", \"temperature\":25.6, \"message\":\"Hello from ESP32\"}";

        TickType_t startPost = xTaskGetTickCount();
        std::string postResp = HttpClient::post(postUrl, jsonData);
        TickType_t costPost = (xTaskGetTickCount() - startPost) * portTICK_PERIOD_MS;

        if (!postResp.empty()) {
            ESP_LOGI(TAG, "POST Response (%lu ms):\n%s", (unsigned long)costPost, postResp.c_str());
        } else {
            ESP_LOGW(TAG, "POST request returned empty response (cost %lu ms)", (unsigned long)costPost);
        }

        vTaskDelay(100);
    }
}


// ============================================================
// app_main
// ============================================================
extern "C" void app_main(void) {
    // --------------------------------------------------------
    // 连接现有路由器
    // --------------------------------------------------------
    app_wifi_main();
    test_http_client();
}

测试

js 复制代码
 let k=0;
 app.get("/apiGet",(req,res)=>{
    res.send(M.successResult(k++));
 })

 app.post("/apiPost",(req,res)=>{
	console.log(req.params); 
    res.send(M.successResult(k++));
 })
bash 复制代码
[16:16:33.160]收←◆I (20799) HttpClient: GET http://192.168.3.38:8888/apiGet
I (20809) HttpClient: HTTP Status = 200
I (20819) HTTP_TEST: GET Response (20 ms):
{"code":0,"msg":"success","data":352}

[16:16:35.180]收←◆I (22819) HttpClient: POST http://192.168.3.38:8888/apiPost

[16:16:35.243]收←◆I (22879) HttpClient: HTTP Status = 200
I (22879) HTTP_TEST: POST Response (60 ms):
{"code":0,"msg":"success","data":353}

TcpClient.hpp

c 复制代码
#ifndef TCP_CLIENT_HPP
#define TCP_CLIENT_HPP

#include <string>
#include <cstring>
#include "esp_log.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"

class TcpClient {
private:
    static const char* TAG;

    int sock_;
    std::string ip_;
    int port_;

public:
    TcpClient() : sock_(-1), port_(0) {}

    ~TcpClient() {
        close();
    }

    // 禁止拷贝
    TcpClient(const TcpClient&) = delete;
    TcpClient& operator=(const TcpClient&) = delete;

    // 连接服务器
    bool connect(const char* ip, uint16_t port, uint32_t timeout_ms = 5000) {
        if (sock_ >= 0) {
            ESP_LOGW(TAG, "Already connected, close first");
            close();
        }

        sock_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (sock_ < 0) {
            ESP_LOGE(TAG, "Failed to create socket: errno %d", errno);
            return false;
        }

        // 设置收发超时
        struct timeval tv;
        tv.tv_sec = timeout_ms / 1000;
        tv.tv_usec = (timeout_ms % 1000) * 1000;
        setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
        setsockopt(sock_, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(port);
        inet_pton(AF_INET, ip, &server_addr.sin_addr);

        ESP_LOGI(TAG, "Connecting to %s:%d", ip, port);

        int err = ::connect(sock_, (struct sockaddr*)&server_addr, sizeof(server_addr));
        if (err != 0) {
            ESP_LOGE(TAG, "Connect failed: errno %d", errno);
            ::close(sock_);
            sock_ = -1;
            return false;
        }

        ip_ = ip;
        port_ = port;
        ESP_LOGI(TAG, "Connected to %s:%d", ip, port);
        return true;
    }

    // 发送数据
    int32_t write(const void* buffer, uint32_t size, uint32_t timeout_ms = 5000) {
        if (sock_ < 0) {
            ESP_LOGE(TAG, "Not connected");
            return -1;
        }

        if (buffer == nullptr || size == 0) {
            ESP_LOGE(TAG, "Invalid buffer or size");
            return -1;
        }

        // 更新发送超时
        struct timeval tv;
        tv.tv_sec = timeout_ms / 1000;
        tv.tv_usec = (timeout_ms % 1000) * 1000;
        setsockopt(sock_, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

        int sent = ::send(sock_, buffer, size, 0);
        if (sent < 0) {
            ESP_LOGE(TAG, "Send failed: errno %d", errno);
        } else {
            ESP_LOGI(TAG, "Sent %d/%u bytes", sent, size);
        }
        return sent;
    }

    // 接收数据
    int32_t read(void* buffer, uint32_t size, uint32_t timeout_ms = 5000) {
        if (sock_ < 0) {
            ESP_LOGE(TAG, "Not connected");
            return -1;
        }

        if (buffer == nullptr || size == 0) {
            ESP_LOGE(TAG, "Invalid buffer or size");
            return -1;
        }

        // 更新接收超时
        struct timeval tv;
        tv.tv_sec = timeout_ms / 1000;
        tv.tv_usec = (timeout_ms % 1000) * 1000;
        setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

        int len = ::recv(sock_, buffer, size, 0);
        if (len > 0) {
            ESP_LOGI(TAG, "Received %d bytes", len);
        } else if (len == 0) {
            ESP_LOGW(TAG, "Connection closed by peer");
        } else {
            ESP_LOGE(TAG, "Recv failed: errno %d", errno);
        }
        return len;
    }

    // 关闭连接
    void close() {
        if (sock_ >= 0) {
            ::close(sock_);
            sock_ = -1;
            ESP_LOGI(TAG, "Socket closed");
        }
    }

    // 是否已连接
    bool isConnected() const {
        return sock_ >= 0;
    }
};

const char* TcpClient::TAG = "TcpClient";

#endif // TCP_CLIENT_HPP

main.cpp

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "app_wifi.h"
#include "TcpClient.hpp"

static const char *TAG = "TCP_TEST";
static const char* SERVER_IP = "192.168.3.38";
static const int SERVER_PORT = 8888;

static void test_tcp_client(void)
{
    TcpClient tcp;

    while (1) {
        // --- 连接服务器 ---
        if (!tcp.connect(SERVER_IP, SERVER_PORT)) {
            ESP_LOGW(TAG, "Connect failed, retry after 2s");
            vTaskDelay(pdMS_TO_TICKS(2000));
            continue;
        }

        // --- 测试 write ---
        const char* msg = "Hello from ESP32";
        TickType_t startWrite = xTaskGetTickCount();
        int32_t sent = tcp.write(msg, strlen(msg), 3000);
        TickType_t costWrite = (xTaskGetTickCount() - startWrite) * portTICK_PERIOD_MS;

        if (sent > 0) {
            ESP_LOGI(TAG, "Write %d bytes (%lu ms): %s", sent, (unsigned long)costWrite, msg);
        } else {
            ESP_LOGW(TAG, "Write failed (cost %lu ms)", (unsigned long)costWrite);
            tcp.close();
            vTaskDelay(pdMS_TO_TICKS(2000));
            continue;
        }

        // --- 测试 read ---
        char buf[256] = {0};
        TickType_t startRead = xTaskGetTickCount();
        int32_t len = tcp.read(buf, sizeof(buf) - 1, 3000);
        TickType_t costRead = (xTaskGetTickCount() - startRead) * portTICK_PERIOD_MS;
        if (len > 0) {
            buf[len] = '\0';
            ESP_LOGI(TAG, "Read %d bytes (%lu ms): %s", len, (unsigned long)costRead, buf);
        } else if (len == 0) {
            ESP_LOGW(TAG, "Connection closed by peer (cost %lu ms)", (unsigned long)costRead);
        } else {
            ESP_LOGW(TAG, "Read failed (cost %lu ms)", (unsigned long)costRead);
        }

        tcp.close();
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

// ============================================================
// app_main
// ============================================================
extern "C" void app_main(void) {
    // --------------------------------------------------------
    // 连接现有路由器
    // --------------------------------------------------------
    app_wifi_main();
    test_tcp_client();
}

测试

bash 复制代码
[16:32:10.115]收←◆I (22239) TcpClient: Connecting to 192.168.3.38:8888
I (22239) TcpClient: Connected to 192.168.3.38:8888
I (22239) TcpClient: Sent 16/16 bytes
I (22239) TCP_TEST: Write 16 bytes (0 ms): Hello from ESP32
I (22259) TcpClient: Received 47 bytes
I (22259) TCP_TEST: Read 47 bytes (10 ms): HTTP/1.1 400 Bad Request
Connection: close
I (22259) TcpClient: Socket closed

UdpClient.hpp

c 复制代码
#ifndef UDP_CLIENT_HPP
#define UDP_CLIENT_HPP

#include <string>
#include <cstring>
#include "esp_log.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"

class UdpClient {
private:
    static const char* TAG;

    int sock_;
    std::string ip_;
    uint16_t port_;

public:
    UdpClient() : sock_(-1), port_(0) {}

    ~UdpClient() {
        close();
    }

    // 禁止拷贝
    UdpClient(const UdpClient&) = delete;
    UdpClient& operator=(const UdpClient&) = delete;

    // 创建 UDP socket 并绑定目标地址
    bool open(const char* ip, uint16_t port, uint16_t local_port = 0, uint32_t timeout_ms = 5000) {
        if (sock_ >= 0) {
            ESP_LOGW(TAG, "Already opened, close first");
            close();
        }

        sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (sock_ < 0) {
            ESP_LOGE(TAG, "Failed to create socket: errno %d", errno);
            return false;
        }

        // 绑定本地端口
        if (local_port > 0) {
            struct sockaddr_in local_addr;
            memset(&local_addr, 0, sizeof(local_addr));
            local_addr.sin_family = AF_INET;
            local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
            local_addr.sin_port = htons(local_port);

            if (bind(sock_, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {
                ESP_LOGE(TAG, "Bind to local port %d failed: errno %d", local_port, errno);
                ::close(sock_);
                sock_ = -1;
                return false;
            }
            ESP_LOGI(TAG, "Bound to local port %d", local_port);
        }

        // 设置收发超时
        struct timeval tv;
        tv.tv_sec = timeout_ms / 1000;
        tv.tv_usec = (timeout_ms % 1000) * 1000;
        setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
        setsockopt(sock_, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

        ip_ = ip;
        port_ = port;

        ESP_LOGI(TAG, "UDP socket opened, target %s:%d", ip, port);
        return true;
    }

    // 发送数据到目标地址
    int32_t write(const void* buffer, uint32_t size, uint32_t timeout_ms = 5000) {
        if (sock_ < 0) {
            ESP_LOGE(TAG, "Not opened");
            return -1;
        }

        if (buffer == nullptr || size == 0) {
            ESP_LOGE(TAG, "Invalid buffer or size");
            return -1;
        }

        // 更新发送超时
        struct timeval tv;
        tv.tv_sec = timeout_ms / 1000;
        tv.tv_usec = (timeout_ms % 1000) * 1000;
        setsockopt(sock_, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

        struct sockaddr_in dest_addr;
        memset(&dest_addr, 0, sizeof(dest_addr));
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(port_);
        inet_pton(AF_INET, ip_.c_str(), &dest_addr.sin_addr);

        int sent = sendto(sock_, buffer, size, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));
        if (sent < 0) {
            ESP_LOGE(TAG, "Send failed: errno %d", errno);
        } else {
            ESP_LOGI(TAG, "Sent %d/%u bytes to %s:%d", sent, size, ip_.c_str(), port_);
        }
        return sent;
    }

    // 接收数据
    int32_t read(void* buffer, uint32_t size, uint32_t timeout_ms = 5000) {
        if (sock_ < 0) {
            ESP_LOGE(TAG, "Not opened");
            return -1;
        }

        if (buffer == nullptr || size == 0) {
            ESP_LOGE(TAG, "Invalid buffer or size");
            return -1;
        }

        // 更新接收超时
        struct timeval tv;
        tv.tv_sec = timeout_ms / 1000;
        tv.tv_usec = (timeout_ms % 1000) * 1000;
        setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

        struct sockaddr_in src_addr;
        socklen_t addr_len = sizeof(src_addr);

        int len = recvfrom(sock_, buffer, size, 0, (struct sockaddr*)&src_addr, &addr_len);
        if (len > 0) {
            ESP_LOGI(TAG, "Received %d bytes from %s:%d",
                     len, inet_ntoa(src_addr.sin_addr), ntohs(src_addr.sin_port));
        } else if (len == 0) {
            ESP_LOGW(TAG, "Received empty datagram");
        } else {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                ESP_LOGW(TAG, "Recv timeout");
            } else {
                ESP_LOGE(TAG, "Recv failed: errno %d", errno);
            }
        }
        return len;
    }

    // 关闭 socket
    void close() {
        if (sock_ >= 0) {
            ::close(sock_);
            sock_ = -1;
            ESP_LOGI(TAG, "UDP socket closed");
        }
    }

    // 是否已打开
    bool isOpened() const {
        return sock_ >= 0;
    }
};

const char* UdpClient::TAG = "UdpClient";

#endif // UDP_CLIENT_HPP

main.cpp

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "app_wifi.h"
#include "UdpClient.hpp"

static const char *TAG = "UDP_TEST";
static const char* SERVER_IP = "192.168.3.38";
static const int SERVER_PORT = 8888;

static void test_udp_client(void)
{
    UdpClient udp;

    while (1) {
        // --- 打开 UDP ---
        if (!udp.open(SERVER_IP, SERVER_PORT,8888)) {
            ESP_LOGW(TAG, "Open failed, retry after 2s");
            vTaskDelay(pdMS_TO_TICKS(2000));
            continue;
        }

        // --- 测试 write ---
        const char* msg = "Hello UDP from ESP32";
        TickType_t startWrite = xTaskGetTickCount();
        int32_t sent = udp.write(msg, strlen(msg), 3000);
        TickType_t costWrite = (xTaskGetTickCount() - startWrite) * portTICK_PERIOD_MS;

        if (sent > 0) {
            ESP_LOGI(TAG, "Write %d bytes (%lu ms): %s", sent, (unsigned long)costWrite, msg);
        } else {
            ESP_LOGW(TAG, "Write failed (cost %lu ms)", (unsigned long)costWrite);
            udp.close();
            vTaskDelay(pdMS_TO_TICKS(2000));
            continue;
        }

        // --- 测试 read ---
        char buf[10] = {0};
        TickType_t startRead = xTaskGetTickCount();
        int32_t len = udp.read(buf, sizeof(buf) - 1, 3000);
        TickType_t costRead = (xTaskGetTickCount() - startRead) * portTICK_PERIOD_MS;

        if (len > 0) {
            buf[len] = '\0';
            ESP_LOGI(TAG, "Read %d bytes (%lu ms): %s", len, (unsigned long)costRead, buf);
        } else if (len == 0) {
            ESP_LOGW(TAG, "Received empty datagram (cost %lu ms)", (unsigned long)costRead);
        } else {
            ESP_LOGW(TAG, "Read failed or timeout (cost %lu ms)", (unsigned long)costRead);
        }

        udp.close();
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

// ============================================================
// app_main
// ============================================================
extern "C" void app_main(void) {
    // --------------------------------------------------------
    // 连接现有路由器
    // --------------------------------------------------------
    app_wifi_main();
    test_udp_client();
}

测试

c 复制代码
[16:52:26.395]收←◆I (140459) UdpClient: Bound to local port 8888
I (140459) UdpClient: UDP socket opened, target 192.168.3.38:8888
I (140459) UdpClient: Sent 20/20 bytes to 192.168.3.38:8888
I (140459) UDP_TEST: Write 20 bytes (0 ms): Hello UDP from ESP32

[16:52:29.404]收←◆W (143469) UdpClient: Recv timeout
W (143469) UDP_TEST: Read failed or timeout (cost 3000 ms)
I (143469) UdpClient: UDP socket closed

WebSocketClient.hpp

c 复制代码
#ifndef WEBSOCKET_CLIENT_HPP
#define WEBSOCKET_CLIENT_HPP

#include <string>
#include <cstring>
#include <functional>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_websocket_client.h"

class WebSocketClient {
public:
    // 数据回调类型:(data, len, opcode)
    using DataCallback = std::function<void(const uint8_t*, int, uint8_t)>;
    // 事件回调类型:(event_id)
    using EventCallback = std::function<void(int32_t)>;

private:
    static const char* TAG;

    esp_websocket_client_handle_t handle_;
    SemaphoreHandle_t connSem_;   // 连接/断开信号量
    bool connected_;

    DataCallback onData_;
    EventCallback onEvent_;

    // 静态事件处理函数(转发到实例方法)
    static void eventHandler(void* handler_args, esp_event_base_t base,
                             int32_t event_id, void* event_data) {
        auto* self = static_cast<WebSocketClient*>(handler_args);
        if (self) {
            self->onWsEvent(event_id, (esp_websocket_event_data_t*)event_data);
        }
    }

    void onWsEvent(int32_t event_id, esp_websocket_event_data_t* data) {
        switch (event_id) {
            case WEBSOCKET_EVENT_CONNECTED:
                ESP_LOGI(TAG, "Connected");
                connected_ = true;
                if (connSem_) xSemaphoreGive(connSem_);
                break;

            case WEBSOCKET_EVENT_DISCONNECTED:
                ESP_LOGW(TAG, "Disconnected");
                connected_ = false;
                if (connSem_) xSemaphoreGive(connSem_);
                break;

            case WEBSOCKET_EVENT_CLOSED:
                ESP_LOGI(TAG, "Closed cleanly");
                connected_ = false;
                if (connSem_) xSemaphoreGive(connSem_);
                break;

            case WEBSOCKET_EVENT_DATA:
                if (data) {
                    ESP_LOGD(TAG, "Recv opcode=%d, len=%d, total=%d, offset=%d",
                             data->op_code, data->data_len,
                             data->payload_len, data->payload_offset);

                    // 关闭帧
                    if (data->op_code == 0x08 && data->data_len >= 2) {
                        int code = (data->data_ptr[0] << 8) | data->data_ptr[1];
                        ESP_LOGW(TAG, "Close frame, code=%d", code);
                    }

                    // 用户回调
                    if (onData_ && data->data_ptr && data->data_len > 0) {
                        onData_((const uint8_t*)data->data_ptr, data->data_len, data->op_code);
                    }
                }
                break;

            case WEBSOCKET_EVENT_ERROR:
                ESP_LOGE(TAG, "Error event");
                break;
        }

        // 用户事件回调
        if (onEvent_) {
            onEvent_(event_id);
        }
    }

public:
    WebSocketClient()
            : handle_(nullptr)
            , connSem_(nullptr)
            , connected_(false) {}

    ~WebSocketClient() {
        close();
        if (connSem_) {
            vSemaphoreDelete(connSem_);
            connSem_ = nullptr;
        }
    }

    // 禁止拷贝
    WebSocketClient(const WebSocketClient&) = delete;
    WebSocketClient& operator=(const WebSocketClient&) = delete;

    // 连接 WebSocket 服务器
    // uri 格式:ws://host:port/path 或 wss://host:port/path
    bool connect(const char* uri, uint32_t timeout_ms = 5000,
                 const char* subprotocol = nullptr,
                 const char* cert_pem = nullptr) {
        if (handle_) {
            ESP_LOGW(TAG, "Already created, close first");
            close();
        }

        // 创建信号量
        if (!connSem_) {
            connSem_ = xSemaphoreCreateBinary();
        }

        // 配置
        esp_websocket_client_config_t cfg = {};
        cfg.uri = uri;
        cfg.subprotocol = subprotocol;
        cfg.cert_pem = cert_pem;
        cfg.buffer_size = 2048;
        cfg.task_stack = 4096;
        cfg.task_prio = 5;
        cfg.pingpong_timeout_sec = 30;
        cfg.ping_interval_sec = 10;

        handle_ = esp_websocket_client_init(&cfg);
        if (!handle_) {
            ESP_LOGE(TAG, "Failed to init websocket client");
            return false;
        }

        // 注册事件
        esp_websocket_register_events(handle_, WEBSOCKET_EVENT_ANY,
                                      eventHandler, this);

        ESP_LOGI(TAG, "Connecting to %s", uri);

        // 启动连接
        esp_err_t err = esp_websocket_client_start(handle_);
        if (err != ESP_OK) {
            ESP_LOGE(TAG, "Start failed: %s", esp_err_to_name(err));
            esp_websocket_client_destroy(handle_);
            handle_ = nullptr;
            return false;
        }

        // 等待连接建立
        if (xSemaphoreTake(connSem_, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
            ESP_LOGE(TAG, "Connect timeout (%lu ms)", (unsigned long)timeout_ms);
            esp_websocket_client_stop(handle_);
            esp_websocket_client_destroy(handle_);
            handle_ = nullptr;
            return false;
        }

        if (!connected_) {
            ESP_LOGE(TAG, "Connect failed");
            esp_websocket_client_destroy(handle_);
            handle_ = nullptr;
            return false;
        }

        ESP_LOGI(TAG, "Connected to %s", uri);
        return true;
    }

    // 发送文本
    int32_t write(const void* buffer, uint32_t size, uint32_t timeout_ms = 5000) {
        if (!handle_ || !connected_) {
            ESP_LOGE(TAG, "Not connected");
            return -1;
        }
        if (!buffer || size == 0) {
            ESP_LOGE(TAG, "Invalid buffer or size");
            return -1;
        }

        int sent = esp_websocket_client_send_text(
                handle_, (const char*)buffer, (int)size,
                pdMS_TO_TICKS(timeout_ms));

        if (sent > 0) {
            ESP_LOGI(TAG, "Sent %d bytes (text)", sent);
        } else {
            ESP_LOGE(TAG, "Send text failed");
        }
        return sent;
    }

    // 发送二进制
    int32_t writeBin(const void* buffer, uint32_t size, uint32_t timeout_ms = 5000) {
        if (!handle_ || !connected_) {
            ESP_LOGE(TAG, "Not connected");
            return -1;
        }
        if (!buffer || size == 0) {
            ESP_LOGE(TAG, "Invalid buffer or size");
            return -1;
        }

        int sent = esp_websocket_client_send_bin(
                handle_, (const char*)buffer, (int)size,
                pdMS_TO_TICKS(timeout_ms));

        if (sent > 0) {
            ESP_LOGI(TAG, "Sent %d bytes (binary)", sent);
        } else {
            ESP_LOGE(TAG, "Send binary failed");
        }
        return sent;
    }

    // 发送 ping
    bool ping() {
        if (!handle_ || !connected_) return false;
        // 官方没有 send_ping API,通过底层 send 发送 ping 控制帧
        // 实际上依赖自动 ping 即可,这里仅作兼容
        ESP_LOGW(TAG, "Manual ping not supported, using auto ping instead");
        return true;
    }

    // 关闭连接
    void close() {
        if (handle_) {
            ESP_LOGI(TAG, "Closing...");
            esp_websocket_client_close(handle_, pdMS_TO_TICKS(3000));
            esp_websocket_client_destroy(handle_);
            handle_ = nullptr;
            connected_ = false;
            ESP_LOGI(TAG, "Closed");
        }
    }

    // 是否已连接
    bool isConnected() const {
        return connected_;
    }

    // 设置数据接收回调
    void setDataCallback(DataCallback cb) {
        onData_ = cb;
    }

    // 设置事件回调
    void setEventCallback(EventCallback cb) {
        onEvent_ = cb;
    }
};

const char* WebSocketClient::TAG = "WebSocketClient";

#endif // WEBSOCKET_CLIENT_HPP

main/main.cpp

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "app_wifi.h"
#include "WebSocketClient.hpp"


static const char *TAG = "WEBSOCKET_TEST";
static const char* SERVER_IP = "192.168.3.11";
static const int SERVER_PORT = 8001;

static void test_websocket_client(void)
{
    WebSocketClient ws;

    // 设置数据接收回调
    ws.setDataCallback([&](const uint8_t* data, int len, uint8_t opcode) {
        // opcode: 0x01=文本, 0x02=二进制
        if (opcode == 0x01) {
            ESP_LOGI(TAG, "Recv text: %.*s", len, (const char*)data);
        } else if (opcode == 0x02) {
            ESP_LOGI(TAG, "Recv binary, len=%d", len);
        }
    });

    // 设置事件回调(可选)
    ws.setEventCallback([&](int32_t event_id) {
        if (event_id == WEBSOCKET_EVENT_DISCONNECTED) {
            ESP_LOGW(TAG, "Connection lost");
        }
    });

    // 构造 URI
    char uri[128];
    snprintf(uri, sizeof(uri), "ws://%s:%d/ws", SERVER_IP, SERVER_PORT);

    // 连接
    if (!ws.connect(uri, 5000)) {
        ESP_LOGE(TAG, "Connect failed");
        return;
    }

    // 发送文本
    const char* msg = "Hello WebSocket from ESP32";
    ws.write(msg, strlen(msg));

    // 发送二进制
    uint8_t bin[] = {0x01, 0x02, 0x03, 0x04};
    ws.writeBin(bin, sizeof(bin));

    // 保持运行,接收数据靠回调
    vTaskDelay(pdMS_TO_TICKS(30000));

    // 关闭
    ws.close();
    ESP_LOGI(TAG, "Test done");
}



// ============================================================
// app_main
// ============================================================
extern "C" void app_main(void) {
    // --------------------------------------------------------
    // 连接现有路由器
    // --------------------------------------------------------
    app_wifi_main();
    test_websocket_client();
}

main/idf_component.yml

yml 复制代码
dependencies:
  idf: ">=5.0"
  espressif/esp_websocket_client:
    version: "^1.0.0"

main/CMakeLists.txt

bash 复制代码
idf_component_register(SRCS "main.cpp" "app_wifi.cpp"
                    INCLUDE_DIRS "."
                    REQUIRES esp_websocket_client esp_wifi esp_event nvs_flash esp_netif
)

测试

bash 复制代码
[17:12:05.716]收←◆I (2166) WEBSOCKET_TEST: Recv text: {"id":0,"method":"connect","params":{"clientCount":2,"clientId":2988711024},"slaveInstId":null,"timestamp":22528829}
I (2186) WEBSOCKET_TEST: Recv binary, len=2048
I (2186) WEBSOCKET_TEST: Recv binary, len=2048
I (2186) WEBSOCKET_TEST: Recv binary, len=2048
相关推荐
学运维的Kysan1 小时前
暑假运维学习打卡第二十五天8.16
学习
键盘飞行员2 小时前
Flutter App 全套实战学习计划:从零搭建到 APK 部署
学习·flutter
会编程的吕洞宾3 小时前
DeepAgents In Action学习(Second)
android·java·学习
我爱cope4 小时前
【计算机网络 | 数据链路层7:VLAN:一台交换机如何划分多个逻辑局域网?】
网络·学习·计算机网络
babe小鑫4 小时前
2026信管专业学习数据分析的价值
学习·数据挖掘·数据分析
编程圈子5 小时前
电机驱动开发学习25. 弱磁控制与全速域增益调度
驱动开发·学习
Be for thing5 小时前
【嵌入式成长3】STC89C51外部中断|中断原理、寄存器、中断服务函数
单片机·嵌入式硬件·学习
优化Henry6 小时前
学习笔记之爱立信站点指向证书执行
运维·服务器·网络·笔记·学习·信息与通信
IT古董8 小时前
【WMS学习笔记系列】03-功能模块设计
笔记·学习·系统架构