标题更新:[Nodejs用Node实现WebSocket协议] - 原:[WebSocket在高并发和大数据情况下的传输问题]

标题更新:[Nodejs用Node实现WebSocket协议] - 原:[WebSocket在高并发和大数据情况下的传输问题]

下面是之前遇到的问题:

上周在班里做了关于WebSocket协议的技术分享,因为当时演示的是小数据、间歇性数据发送。所以很成功,并没有遇到啥问题。

不过回去之后发现用一个10000次的循环不断向服务器发送数据时,不论数据的大小,后几千次的数据都变成了乱码:

for (i = 0; i < 10000; i++) ws.send('a');

另外一个问题是,当我发送了一次长度为65535 byte的数据(也就是Payload_len = 126, Payload length = Math.pow(2, 16) - 1),服务器却得到了两次的数据请求:

第一次是FIN = 1, Opcode = 0, Payload length为65535的Frame;
第二次是FIN = 1, Opcode为随机, MASK也是随机的, Payload length还是随机。。。

可是经过查看发现第一次数据的实际长度往往是Payload length值的一半左右,第二次的数据全是乱码。

下面是WebSocket握手结束后数据解码部分的代码:

function decodeFrame(frame) {
    var counter = 0;
var fin_offset = 7,
    opcode_offset = parseInt(1111, 2),
    mask_offset = 7,
    payload_len_offset = parseInt(1111111, 2);

var FIN = frame[counter] &gt;&gt; fin_offset,
    Opcode = frame[counter++] &amp; opcode_offset,
    MASK = frame[counter] &gt;&gt; mask_offset,
    Payload_len = frame[counter++] &amp; payload_len_offset;

Payload_len === 126 &amp;&amp; 
(Payload_len = frame.readUInt16BE(counter)) &amp;&amp; 
counter += 2;

Payload_len === 127 &amp;&amp; 
(Payload_len = frame.readUInt32BE(counter + 4)) &amp;&amp; 
counter += 8;

var Payload_data = [];

if (MASK) {
    var Masking_key = frame.slice(counter, counter + 4);

    counter += 4;

    for (var i = 0; i &lt; Payload_len; i++) {
        var j = i % 4;
        frame[counter + i] ^= Masking_key[j];
    }
}

// 这里得到上面说的测试结果
return {
    FIN: FIN,
    Opcode: Opcode,
    MASK: MASK,
    Payload_len: Payload_len,
    Payload_data: frame.slice(counter, Payload_len)
}

};

WebSocket协议还有什么细节需要处理还是Buffer无法瞬间接受过大的数据吗?

一直没想明白这两种情况下乱码究竟是怎么产生的。。

求社区神牛指导啊~~


问题终于解决~

实现WebSocket真够让人纠结的,正确的代码已上传至github : https://github.com/abbshr/websocket_talk

在这方面遇到麻烦的同学可以参考参考啦~


4 回复

标题更新:Nodejs用Node实现WebSocket协议

下面是之前遇到的问题:

上周在班里做了关于WebSocket协议的技术分享,因为当时演示的是小数据、间歇性数据发送。所以很成功,并没有遇到啥问题。

不过回去之后发现用一个10000次的循环不断向服务器发送数据时,不论数据的大小,后几千次的数据都变成了乱码:

for (i = 0; i < 10000; i++) ws.send('a');

另外一个问题是,当我发送了一次长度为65535 byte的数据(也就是Payload_len = 126, Payload length = Math.pow(2, 16) - 1),服务器却得到了两次的数据请求:

  • 第一次是FIN = 1, Opcode = 0, Payload length为65535的Frame;
  • 第二次是FIN = 1, Opcode为随机, MASK也是随机的, Payload length还是随机。。。

可是经过查看发现第一次数据的实际长度往往是Payload length值的一半左右,第二次的数据全是乱码。

下面是WebSocket握手结束后数据解码部分的代码:

function decodeFrame(frame) {
    var counter = 0;

    var fin_offset = 7,
        opcode_offset = parseInt('1111', 2),
        mask_offset = 7,
        payload_len_offset = parseInt('1111111', 2);

    var FIN = frame[counter] >> fin_offset,
        Opcode = frame[counter++] & opcode_offset,
        MASK = frame[counter] >> mask_offset,
        Payload_len = frame[counter++] & payload_len_offset;

    if (Payload_len === 126) { 
        Payload_len = frame.readUInt16BE(counter);
        counter += 2;
    }

    if (Payload_len === 127) { 
        Payload_len = frame.readUInt32BE(counter + 4);
        counter += 8;
    }

    var Payload_data = [];

    if (MASK) {
        var Masking_key = frame.slice(counter, counter + 4);

        counter += 4;

        for (var i = 0; i < Payload_len; i++) {
            var j = i % 4;
            frame[counter + i] ^= Masking_key[j];
        }
    }
    
    // 返回解码后的数据
    return {
        FIN: FIN,
        Opcode: Opcode,
        MASK: MASK,
        Payload_len: Payload_len,
        Payload_data: frame.slice(counter, counter + Payload_len)
    };
}

解决方案

在上述代码中,问题主要出在Payload_data的切片上。由于counter的计算错误,导致了数据的不正确切片。以下是修正后的代码:

function decodeFrame(frame) {
    var counter = 0;

    var fin_offset = 7,
        opcode_offset = parseInt('1111', 2),
        mask_offset = 7,
        payload_len_offset = parseInt('1111111', 2);

    var FIN = frame[counter] >> fin_offset,
        Opcode = frame[counter++] & opcode_offset,
        MASK = frame[counter] >> mask_offset,
        Payload_len = frame[counter++] & payload_len_offset;

    if (Payload_len === 126) { 
        Payload_len = frame.readUInt16BE(counter);
        counter += 2;
    }

    if (Payload_len === 127) { 
        Payload_len = frame.readUInt32BE(counter + 4);
        counter += 8;
    }

    var Payload_data = [];

    if (MASK) {
        var Masking_key = frame.slice(counter, counter + 4);

        counter += 4;

        for (var i = 0; i < Payload_len; i++) {
            var j = i % 4;
            frame[counter + i] ^= Masking_key[j];
        }
    }
    
    // 修正后的Payload_data切片
    return {
        FIN: FIN,
        Opcode: Opcode,
        MASK: MASK,
        Payload_len: Payload_len,
        Payload_data: frame.slice(counter, counter + Payload_len)
    };
}

总结

通过上述代码的调整,我们解决了WebSocket数据传输中的乱码问题。WebSocket协议有许多细节需要注意,尤其是在处理大数据传输时。如果还有其他问题,可以参考我的GitHub仓库:WebSocket 实现


WebSocket协议本身定义是没有问题的,应该是你的发送端处理或接收端的处理有问题。

恩,已解决,不过这个问题产生的原因确实很蛋疼。。 的确是读取的问题。要在读取时循环调用解码函数。

解释与解决方案

首先,我们来看一下WebSocket协议的一些核心细节:

  1. FIN(Final Frame):指示这是消息的最后一帧。
  2. Opcode:表示帧的操作类型,例如文本帧(1)、二进制帧(2)等。
  3. Mask:客户端发送的数据是否被掩码处理,服务器端不能设置掩码位。
  4. Payload Length:数据负载的长度。

对于你提到的问题,主要是由于编码和解码逻辑不正确导致的。在发送大数据时,需要分帧处理。以下是正确实现WebSocket协议的一些关键点:

发送数据

当你发送大数据时,应该将其拆分为多个帧,并且每帧都需要正确设置FIN标志位。以下是一个简单的发送函数示例:

function sendLargeData(ws, data) {
    const maxPayloadLength = 125; // 单个帧的最大负载长度
    let remainingData = data;

    while (remainingData.length > 0) {
        const chunk = remainingData.slice(0, maxPayloadLength);
        remainingData = remainingData.slice(maxPayloadLength);

        const isLastChunk = remainingData.length === 0;
        const fin = isLastChunk ? 1 : 0;

        const frame = new ArrayBuffer(10 + chunk.byteLength);
        const view = new DataView(frame);

        view.setUint8(0, fin << 7 | 1); // FIN + OPCODE (1 for text)
        view.setUint8(1, 0); // MASK (not set by server)
        view.setUint8(2, chunk.byteLength); // Payload length

        // Copy the data into the frame
        new Uint8Array(frame, 10).set(new Uint8Array(chunk));

        ws.send(view.buffer);
    }
}

解码数据

解码函数需要正确处理多帧的情况,并且确保所有帧都被正确合并。以下是改进后的解码函数示例:

function decodeFrame(buffer) {
    const view = new DataView(buffer);
    const fin = view.getUint8(0) >> 7;
    const opcode = view.getUint8(0) & 0x0F;
    const mask = view.getUint8(1) >> 7;
    const payloadLength = view.getUint8(1) & 0x7F;

    let offset = 2;

    if (payloadLength === 126) {
        payloadLength = view.getUint16(offset);
        offset += 2;
    } else if (payloadLength === 127) {
        payloadLength = view.getUint32(offset);
        offset += 8;
    }

    const maskingKey = mask ? buffer.slice(offset, offset + 4) : null;
    offset += 4;

    const payload = buffer.slice(offset, offset + payloadLength);

    if (maskingKey) {
        for (let i = 0; i < payloadLength; i++) {
            payload[i] ^= maskingKey[i % 4];
        }
    }

    return {
        fin,
        opcode,
        payloadLength,
        payload
    };
}

通过以上示例,你可以更好地理解和实现WebSocket协议。确保在发送和接收大数据时进行适当的分帧处理,并正确处理FIN标志位以标识消息的结束。

回到顶部