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

Arduino Websocket客户端接收数据乱码问题求助(附代码)

Arduino Websocket客户端接收文本乱码问题排查

我在为Arduino编写Websocket客户端库,目前连接与文本发送功能正常,服务器能正常接收数据,但接收服务器发送的文本时串口输出乱码,怀疑是掩码处理或其他逻辑存在问题。以下是相关代码及串口输出的乱码数据,请求协助排查问题。


processData()和processCompleteFrame()函数

void HsHSocketClient::processData() {
    static uint8_t fragmentedOpcode;
    static String fragmentedPayload;

    while (client.available()) {
        uint8_t opcode = client.read();
        bool fin = (opcode & 0x80) != 0;
        opcode &= 0x0F;
        uint8_t payloadLen = client.read() & 0x7F;

        if (payloadLen == 126) {
            payloadLen = (client.read() << 8) | client.read();
            payloadLen -= 2;  // Subtract 2 to account for the two additional bytes read for length
        } else if (payloadLen == 127) {
            payloadLen = (client.read() << 24) | (client.read() << 16) | (client.read() << 8) | client.read();
            payloadLen -= 8;  // Subtract 8 to account for the eight additional bytes read for length
        }

        uint8_t maskingKey[4];
        client.readBytes(maskingKey, 4);

        Serial.print("[Ws] - Masking Key: ");
        for (size_t i = 0; i < 4; ++i) {
            Serial.print(maskingKey[i], HEX);
            Serial.print(" ");
        }
        Serial.println();

        uint8_t payload[payloadLen];
        client.readBytes(payload, payloadLen);
        for (size_t i = 0; i < payloadLen; ++i) {
            payload[i] ^= maskingKey[i % 4];
        }

        Serial.print("[Ws] - Unmasked Payload: ");
        for (size_t i = 0; i < payloadLen; ++i) {
            Serial.print(payload[i], HEX);
            Serial.print(" ");
        }
        Serial.println();

        if (fin) {
            if (fragmentedPayload.length() > 0) {
                fragmentedPayload += String(payload, payloadLen);
                processCompleteFrame(fragmentedOpcode, fragmentedPayload.c_str(),payloadLen);
                fragmentedPayload = "";
            } else {
                processCompleteFrame(opcode, reinterpret_cast<const char*>(payload),payloadLen);
            }
        } else {
            fragmentedOpcode = opcode;
            fragmentedPayload += String(payload, payloadLen);
        }
    }
}

void HsHSocketClient::processCompleteFrame(uint8_t opcode, const char* payload, size_t payloadLen) {
    Serial.print("[Ws] - Received frame, Opcode: 0x");
    Serial.println(opcode, HEX);

    Serial.print("[Ws] - Payload Hex: ");
    for (size_t i = 0; i < payloadLen; ++i) {
        Serial.print(payload[i], HEX);
        Serial.print(" ");
    }
    Serial.println();

    if (opcode == 0x1) {  // Text frame
        Serial.print("[Ws] - Received text frame: ");
        Serial.println(payload);
    }else if (opcode == 0x2) {  // Binary frame
        Serial.print("[Ws] - Received binary frame: ");
        Serial.println(payload);
    } else if (opcode == 0x8) {  // Close frame
        Serial.print("[Ws] - Received close frame: ");
        Serial.println(payload);
        sendCloseFrame();
        client.stop();
    } else if (opcode == 0x9) {  // Ping frame
        Serial.print("[Ws] - Received ping frame: ");
        Serial.println(payload);
    } else if (opcode == 0xA) {  // Pong frame
        Serial.print("[Ws] - Received pong frame: ");
        Serial.println(payload);
    } else {
        Serial.print("[Ws] - Received unknown frame: ");
        Serial.println(payload);
    }
}

Node.js服务器代码

wss.on('connection', function(ws) {
    console.log("Client connected!");

    ws.on('error', console.error);
  
    ws.on('message', function(data) {
        let msg;
        try {
            msg = JSON.parse(data);
        } catch (error) {
            msg = data;
        }
      console.log('received: %s', msg);
    });

    ws.on("close", function() {
        console.log("Client disconnected!");
    });
  
    setInterval(() => {
        ws.send('Test message');
    }, 5000);
});

串口输出数据

[Ws] - Connecting to server: 192.168.10.104:80
[Ws] - Connected to server
[Ws] - Masking Key: 54 50 2F 31 
[Ws] - Unmasked Payload: 7A 61 F 0 64 61 F 62 23 39 5B 52 3C 39 41 56 74 0 5D 5E 20 3F 4C 5E 38 23 22 3B 1 20 48 43 35 34 4A B 74 27 4A 53 27 3F 4C 5A 31 
24 22 3B 17 3F 41 5F 31 33 5B 58 3B 3E 15 11 1 20 48 43 35 34 4A 3C 5E 3 4A 52 79 7 4A 53 7 3F 4C 5A 31 24 2 70 
[Ws] - Masking Key: 65 70 74 3A 
[Ws] - Unmasked Payload: 45 34 4D E 33 3B 3D 59 3C 14 23 77 57 40 43 5D 1 22 
3B 6D 1 35 46 6B 6 22 3F 63 58 7D 7E 37 6F 5B 66 BA B5 39 8F 5 C9 1 88 5 A1 B8 37 5 75 70 74 3A 64 70 74 3A 6E 71 74 3A ED CC F4 5 FF 5D 66 BA 95 39 8F 5 
51 2C 88 5 6 70 74 3A 75 70 74 3A F1 2C 88 5 64 70 74 3A 45 73 8F 5 1B C1 60 
[Ws] - Masking Key: 54 65 73 74 
[Ws] - Unmasked Payload: 74 8 16 7 27 4 14 11 60 39 8F 4B 
[Ws] - Received frame, Opcode: 0x3
[Ws] - Payload Hex: 7A 61 F 0 64 61 F 62 23 39 5B 52
[Ws] - Received unknown frame: za
[Ws] - Masking Key: 54 65 73 74 
[Ws] - Unmasked Payload: 74 8 16 7 27 4 14 11 60 39 8F 4B 
[Ws] - Received frame, Opcode: 0x1
[Ws] - Payload Hex: 74 8 16 7 27 4 14 11 60 39 8F 4B 
[Ws] - Received text frame: '`9�K
[Ws] - Masking Key: 54 65 73 74 
[Ws] - Unmasked Payload: 74 8 16 7 27 4 14 11 60 39 8F 4B 
[Ws] - Received frame, Opcode: 0x1
[Ws] - Payload Hex: 74 8 16 7 27 4 14 11 60 39 8F 4B
[Ws] - Received text frame: '`9�K

问题排查与修复

1. 错误处理MASK位(核心问题)

Websocket协议规定:服务器发送给客户端的帧不带掩码,只有客户端向服务器发送的帧需要掩码。你当前的代码无条件读取4字节掩码,导致把负载的前4个字节当成掩码读取,后续所有数据错位,这是乱码的根本原因。

修改代码:

// 替换原有的payloadLen读取逻辑
uint8_t secondByte = client.read();
bool masked = (secondByte & 0x80) != 0; // 判断是否带掩码
uint8_t payloadLen = secondByte & 0x7F;

// ... 处理扩展长度 ...

uint8_t maskingKey[4];
if (masked) { // 只有带掩码时才读取掩码
    client.readBytes(maskingKey, 4);
}

// ... 读取payload后,仅在masked为true时解码 ...
if (masked) {
    for (size_t i = 0; i < payloadLen; ++i) {
        payload[i] ^= maskingKey[i % 4];
    }
}

2. 负载长度计算错误

当payloadLen为126或127时,你错误地减去了扩展长度的字节数,导致实际读取的负载长度比真实长度少,后续数据错位。正确的做法是直接使用读取到的扩展长度作为payloadLen:

if (payloadLen == 126) {
    // 读取16位扩展长度
    uint16_t extLen = (static_cast<uint16_t>(client.read()) << 8) | client.read();
    payloadLen = extLen;
} else if (payloadLen == 127) {
    // 读取64位扩展长度(Arduino为32位系统,这里只处理低32位,足够应对常规场景)
    uint32_t extLen = (static_cast<uint32_t>(client.read()) << 24) | 
                      (static_cast<uint32_t>(client.read()) << 16) | 
                      (static_cast<uint32_t>(client.read()) << 8) | 
                      client.read();
    // 跳过高4字节(如果你的应用不需要处理超过4GB的负载)
    client.readBytes(new uint8_t[4], 4);
    payloadLen = extLen;
}

3. 片段帧处理的长度错误

处理拼接后的片段帧时,你传入了当前帧的长度而非整个拼接后字符串的长度,导致打印时截断。修改如下:

if (fin) {
    if (fragmentedPayload.length() > 0) {
        fragmentedPayload += String(reinterpret_cast<char*>(payload), payloadLen);
        // 传入拼接后的总长度
        processCompleteFrame(fragmentedOpcode, fragmentedPayload.c_str(), fragmentedPayload.length());
        fragmentedPayload = "";
    } else {
        processCompleteFrame(opcode, reinterpret_cast<const char*>(payload), payloadLen);
    }
}

4. String构造的截断问题

原代码中String(payload, payloadLen)会把0x00字节当成字符串结束符,导致拼接片段帧时截断。改用String(reinterpret_cast<char*>(payload), payloadLen)可以避免这个问题。


内容的提问来源于stack exchange,提问作者Dr.Random

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 02:07:01