一、简介

ESP32-WROOM-32 是一款高性能、低功耗的 Wi-Fi + 蓝牙双模模组,常用于物联网项目中。WebSocket 是一种全双工通信协议,适合低延迟、高实时性的通信场景(如聊天、远程控制、状态推送等)。

本文将介绍如何使用 ESP-IDF v5.4.0 在 ESP32 上建立 WebSocket 客户端,实现与 WebSocket 服务器的实时通信。

二、开发环境准备

ESP-IDF 版本:v5.4.0

硬件:ESP32-WROOM-32 开发板

WebSocket 测试服务:wss://echo.websocket.events(或搭建自己的)

三、项目结构

## 目录结构

├── CMakeLists.txt # 项目主 CMake 配置文件
├── pytest_hello_world.py # 自动化测试脚本
├── README.md # 项目说明文档
├── sdkconfig* # 配置文件
├── websocket.zip # 相关压缩包
├── .devcontainer/ # VS Code 远程开发容器配置
├── .vscode/ # VS Code 配置
├── build/ # 构建输出目录
├── components/
│ └── wss-client/ # 自定义组件
└── main/
├── CMakeLists.txt # main 目录 CMake 配置
└── hello_world_main.c # 主程序入口


四、完整代码

// 

#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_spi_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_log.h"
#include "wss_client.h"

#define WIFI_SSID          "Xiaomi_F00C"
#define WIFI_PASS          "12345678"
#define WEBSOCKET_SERVER_URI "ws://echo.websocket.events"

static const char *TAG = "ws_example";

static void on_ws_message(const char *msg, size_t len) {
    ESP_LOGI(TAG, "Received WebSocket message: %.*s", (int)len, msg);
}

void app_main(void) {
    printf("Hello world!\n");
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    printf("This is %s chip with %d CPU cores, WiFi%s%s, ",
           CONFIG_IDF_TARGET,
           chip_info.cores,
           (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
           (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
    printf("silicon revision %d, ", chip_info.revision);
    uint32_t size = 0;
    esp_flash_get_size(NULL, &size);
    printf("%luMB %s flash\n", (unsigned long)(size / (1024 * 1024)),
           (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");

    ESP_ERROR_CHECK(nvs_flash_init());
    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_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    wifi_config_t wifi_config = {0};
    strncpy((char *)wifi_config.sta.ssid, WIFI_SSID, sizeof(wifi_config.sta.ssid));
    strncpy((char *)wifi_config.sta.password, WIFI_PASS, sizeof(wifi_config.sta.password));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
    ESP_ERROR_CHECK(esp_wifi_connect());
    printf("Connecting to WiFi SSID: %s ...\n", WIFI_SSID);
    vTaskDelay(5000 / portTICK_PERIOD_MS);

    wss_client_config_t ws_cfg = {
        .uri = WEBSOCKET_SERVER_URI,
        .on_message = on_ws_message,
    };
    wss_client_start(&ws_cfg);
    // 发送消息等可在 wss_client.c 内部实现或扩展
    while (1) {
        vTaskDelay(10000 / portTICK_PERIOD_MS);
    }
}

// wss_client.c
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "wss_client.h"

#define TAG "wss-client"

// 组包 WebSocket 文本帧,返回帧长度
size_t wss_client_build_frame(const char *msg, uint8_t *frame_buf, size_t buf_size) {
    size_t msg_len = strlen(msg);
    if (buf_size < msg_len + 6) return 0; // 缓冲区不足
    frame_buf[0] = 0x81; // FIN + text frame
    frame_buf[1] = 0x80 | (msg_len & 0x7F); // MASK bit + payload len
    uint8_t mask[4];
    for (int i = 0; i < 4; ++i) mask[i] = esp_random() & 0xFF;
    memcpy(&frame_buf[2], mask, 4);
    for (size_t i = 0; i < msg_len; ++i) {
        frame_buf[6 + i] = msg[i] ^ mask[i % 4];
    }
    return msg_len + 6;
}

static void wss_client_task(void *param) {
    const wss_client_config_t *config = (const wss_client_config_t *)param;
    // 这里只做 ws:// 的简单演示,不支持 wss://
    // 实际项目建议用更完整的库或完善此实现
    struct addrinfo hints = {0}, *res;
    // 解析 ws://host[:port][/path]
    char host[128] = {0};
    char path[128] = "/";
    int port = 80;
    const char *p = config->uri + strlen("ws://");
    const char *slash = strchr(p, '/');
    const char *colon = strchr(p, ':');
    if (colon && (!slash || colon < slash)) {
        strncpy(host, p, colon - p);
        port = atoi(colon + 1);
        if (slash) strncpy(path, slash, sizeof(path) - 1);
    } else {
        if (slash) {
            strncpy(host, p, slash - p);
            strncpy(path, slash, sizeof(path) - 1);
        } else {
            strncpy(host, p, sizeof(host) - 1);
        }
    }
    ESP_LOGI(TAG, "Connecting to %s:%d%s", host, port, path);
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    if (getaddrinfo(host, NULL, &hints, &res) != 0) {
        ESP_LOGE(TAG, "DNS lookup failed");
        vTaskDelete(NULL);
        return;
    }
    struct sockaddr_in *addr = (struct sockaddr_in *)res->ai_addr;
    addr->sin_port = htons(port);
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        ESP_LOGE(TAG, "Socket create failed");
        freeaddrinfo(res);
        vTaskDelete(NULL);
        return;
    }
    if (connect(sock, (struct sockaddr *)addr, sizeof(struct sockaddr_in)) != 0) {
        ESP_LOGE(TAG, "Socket connect failed");
        close(sock);
        freeaddrinfo(res);
        vTaskDelete(NULL);
        return;
    }
    freeaddrinfo(res);
    // 发送 WebSocket 握手
    char req[512];
    snprintf(req, sizeof(req),
        "GET %s HTTP/1.1\r\n"
        "Host: %s:%d\r\n"
        "Upgrade: websocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
        "Sec-WebSocket-Version: 13\r\n"
        "\r\n",
        path, host, port);
    send(sock, req, strlen(req), 0);
    char resp[512];
    int len = recv(sock, resp, sizeof(resp)-1, 0);
    if (len <= 0) {
        ESP_LOGE(TAG, "Handshake failed");
        close(sock);
        vTaskDelete(NULL);
        return;
    }
    resp[len] = 0;
    ESP_LOGI(TAG, "Handshake response: %s", resp);
    // 简单循环收消息和发消息
    while (1) {
        const char *msg = "Hello from ESP32";
        uint8_t frame[128] = {0};
        size_t frame_len = wss_client_build_frame(msg, frame, sizeof(frame));
        if (frame_len > 0) {
            send(sock, frame, frame_len, 0);
            ESP_LOGI(TAG, "Sent: %s", msg);
        }
        // 接收服务器返回
        uint8_t hdr[2];
        int r = recv(sock, hdr, 2, 0);
        if (r <= 0) break;
        int payload_len = hdr[1] & 0x7F;
        char recv_msg[128] = {0};
        if (payload_len > 0 && payload_len < sizeof(recv_msg)) {
            r = recv(sock, recv_msg, payload_len, 0);
            if (r > 0 && config->on_message) {
                config->on_message(recv_msg, r);
            }
        }
        vTaskDelay(5000 / portTICK_PERIOD_MS);
    }
    close(sock);
    vTaskDelete(NULL);
}

void wss_client_start(const wss_client_config_t *config) {
    xTaskCreate(wss_client_task, "wss_client", 4096, (void *)config, 5, NULL);
}

#pragma once
#include <stdint.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef void (*wss_on_message_cb)(const char *msg, size_t len);

typedef struct {
    const char *uri;
    wss_on_message_cb on_message;
} wss_client_config_t;

void wss_client_start(const wss_client_config_t *config);

#ifdef __cplusplus
}
#endif

五、运行结果

代码运行,自动连接指定的wifi,向目标服务地址发送Hello from ESP32,会实时收到相同的数据
在这里插入图片描述

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐