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

NodeJS与ESP32通过WebSocket传输时如何识别二进制与文本消息

解决方案:WebSocket消息类型区分(ESP32-Cam + Node.js ws模块)

方案1:自定义二进制消息头部(通用高效)

通过在二进制数据前添加固定长度的标识头部,直接把类型信息嵌入消息中,无需额外发送标记消息,效率高且无同步问题。

ESP32-Cam端代码修改

发送帧数据前,拼接4字节的类型标识(比如STRM表示视频流,THUM表示缩略图),再发送拼接后的二进制数据:

void liveCam(uint8_t num){
    camera_fb_t * fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Frame buffer could not be acquired");
        return;
    }

    // 4字节ASCII标识,自定义类型
    uint8_t header[] = {'S', 'T', 'R', 'M'}; // STRM = 视频流
    size_t totalLen = sizeof(header) + fb->len;
    
    // 分配临时缓冲区存储头部+帧数据
    uint8_t *combinedBuf = (uint8_t*)malloc(totalLen);
    if (!combinedBuf) {
        esp_camera_fb_return(fb);
        Serial.println("Failed to allocate buffer for combined message");
        return;
    }

    // 复制头部和帧数据到缓冲区
    memcpy(combinedBuf, header, sizeof(header));
    memcpy(combinedBuf + sizeof(header), fb->buf, fb->len);

    // 发送拼接后的二进制消息
    webSocket.sendBIN(num, combinedBuf, totalLen);

    // 释放资源
    free(combinedBuf);
    esp_camera_fb_return(fb);
}

// 发送缩略图示例
void sendThumbnail(uint8_t num, uint8_t* thumbBuf, size_t thumbLen){
    uint8_t header[] = {'T', 'H', 'U', 'M'}; // THUM = 缩略图
    size_t totalLen = sizeof(header) + thumbLen;
    uint8_t *combinedBuf = (uint8_t*)malloc(totalLen);
    if (!combinedBuf) return;

    memcpy(combinedBuf, header, sizeof(header));
    memcpy(combinedBuf + sizeof(header), thumbBuf, thumbLen);
    webSocket.sendBIN(num, combinedBuf, totalLen);
    free(combinedBuf);
}

Node.js端(ws模块)处理代码

收到消息后先判断类型,二进制消息读取前4字节标识再处理对应数据:

this.socket.on("connection", (ws) => {
    ws.on("message", (msg) => {
        if (typeof msg === 'string') {
            // 处理JSON文本指令,按原有tag字段区分
            const data = JSON.parse(msg);
            switch(data.tag) {
                case 'blink':
                    // 执行闪烁逻辑
                    break;
                case 'fetch_temp':
                    // 执行温度获取逻辑
                    break;
                default:
                    console.log("Unknown text command:", data);
            }
        } else if (msg instanceof ArrayBuffer) {
            const uint8Arr = new Uint8Array(msg);
            // 读取前4字节类型标识
            const typeTag = String.fromCharCode(...uint8Arr.slice(0, 4));
            // 提取实际二进制 payload
            const payload = uint8Arr.slice(4);

            switch(typeTag) {
                case 'STRM':
                    // 转发视频流到前端或存储
                    // 例如:broadcastToAllClients(payload);
                    break;
                case 'THUM':
                    // 处理缩略图数据
                    break;
                default:
                    console.log("Unknown binary type:", typeTag);
            }
        }
    });
});

方案2:利用WebSocket自定义Opcode(零额外开销)

WebSocket协议保留了部分Opcode(0x3-0x7、0xB-0xF)可用于自定义消息类型,无额外数据开销,效率最高,但需要ESP端WebSocket库支持自定义Opcode发送。

ESP32-Cam端代码修改

如果WebSocket库提供指定Opcode的发送方法(如sendFrame),直接用自定义Opcode标记:

void liveCam(uint8_t num){
    camera_fb_t * fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Frame buffer could not be acquired");
        return;
    }

    // 用Opcode 0x3标记视频流,0x4标记缩略图
    webSocket.sendFrame(num, 0x3, fb->buf, fb->len);

    esp_camera_fb_return(fb);
}

void sendThumbnail(uint8_t num, uint8_t* thumbBuf, size_t thumbLen){
    webSocket.sendFrame(num, 0x4, thumbBuf, thumbLen);
}

Node.js端(ws模块)处理代码

ws模块的message事件返回flags参数,包含消息Opcode,直接通过Opcode判断类型:

this.socket.on("connection", (ws) => {
    ws.on("message", (msg, flags) => {
        if (flags.opcode === 1) { // 文本消息
            const data = JSON.parse(msg);
            switch(data.tag) {
                case 'blink':
                    // 处理逻辑
                    break;
                case 'fetch_temp':
                    // 处理逻辑
                    break;
            }
        } else if (flags.opcode === 3) { // 自定义Opcode:视频流
            // msg为ArrayBuffer,处理视频流
        } else if (flags.opcode === 4) { // 自定义Opcode:缩略图
            // 处理缩略图
        } else if (flags.opcode === 2) { // 普通二进制消息(如果有)
            // 处理普通二进制
        }
    });
});

方案3:前置标记消息(兼容旧库,需处理同步)

如果上述方案无法实现,可先发送文本标记、再发送对应二进制,但需处理消息同步(如用序列号匹配),缺点是增加网络开销,可能影响帧率。

ESP32-Cam端代码

static uint32_t streamSeq = 0;
void liveCam(uint8_t num){
    camera_fb_t * fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Frame buffer could not be acquired");
        return;
    }

    // 先发送标记消息,包含类型和序列号
    String tagMsg = "{\"type\":\"stream\",\"seq\":" + String(streamSeq) + "}";
    webSocket.sendTXT(num, tagMsg.c_str());

    // 发送视频帧
    webSocket.sendBIN(num, fb->buf, fb->len);

    streamSeq++;
    esp_camera_fb_return(fb);
}

Node.js端代码

this.socket.on("connection", (ws) => {
    let nextMsgInfo = null; // 存储下一个二进制消息的类型和序列号

    ws.on("message", (msg) => {
        if (typeof msg === 'string') {
            const data = JSON.parse(msg);
            if (data.type && data.seq) {
                // 记录下一个二进制消息的信息
                nextMsgInfo = { type: data.type, seq: data.seq };
            } else {
                // 处理普通JSON指令
                switch(data.tag) {
                    case 'blink':
                        // 处理逻辑
                        break;
                    case 'fetch_temp':
                        // 处理逻辑
                        break;
                }
            }
        } else if (msg instanceof ArrayBuffer && nextMsgInfo) {
            if (nextMsgInfo.type === 'stream') {
                // 处理视频流
            } else if (nextMsgInfo.type === 'thumbnail') {
                // 处理缩略图
            }
            // 重置标记,准备接收下一组消息
            nextMsgInfo = null;
        }
    });
});

方案选择建议

  • 优先选方案2:ESP端支持自定义Opcode时,零额外开销,效率最高。
  • 其次选方案1:兼容性强,开销极小(仅4字节),无同步问题。
  • 方案3仅作为备选:适合无法修改二进制消息结构的场景,但需注意网络丢包可能导致的消息错位。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 03:33:29