如何在WebSockets hybi 08+中构建/解构数据帧?

8
自Chrome更新到v14以来,他们从草案的第三版转向了草案的第八版
我有一个运行在WebSocket上的内部聊天应用程序,虽然我已经让新的握手协议正常工作,但数据帧格式似乎也已经发生变化。我的WebSocket服务器基于Nugget
是否有人已经使用草案第八版使WebSocket正常工作,并且有一个关于如何对传输的数据进行数据帧组装的示例?
3个回答

17

(另请参见:如何在服务器端发送和接收WebSocket消息?)


这很容易,但重要的是要了解格式。

第一个字节几乎总是1000 0001,其中1表示“最后一帧”,三个0是暂时没有任何意义的保留位,0001表示它是文本帧(Chrome使用ws.send()方法发送)。

更新: Chrome现在也可以使用ArrayBuffer发送二进制帧。第一个字节的最后四位将为0002,因此您可以区分文本和二进制数据。数据的解码方式完全相同。)

第二个字节包含一个1(表示它被“屏蔽”(编码)),后面是七个位数,表示帧大小。如果它在000 0000111 1101之间,那就是大小。如果是111 1110,则下面2个字节是长度(因为它不适合7位),如果是111 1111,则下面8个字节是长度(如果它也不适合两个字节)。

随后是四个字节,它们是“掩码”,你需要用它们解码帧数据。这是使用异或编码进行的,该编码使用数据的indexOfByteInData mod 4中定义的掩码之一。解码的方法很简单,就像encodedByte xor maskByte一样(其中maskByteindexOfByteInData mod 4)。

现在我必须说我对C#一窍不通,但这是伪代码(有点JavaScript口音,恐怕):

var length_code = bytes[1] & 127, // remove the first 1 by doing '& 127'
    masks,
    data;

if(length_code === 126) {
    masks = bytes.slice(4, 8);   // 'slice' returns part of the byte array
    data  = bytes.slice(8);      // and accepts 'start' (inclusively)
} else if(length_code === 127) { // and 'end' (exclusively) as arguments
    masks = bytes.slice(10, 14); // Passing no 'end' makes 'end' the length
    data  = bytes.slice(14);     // of the array
} else {
    masks = bytes.slice(2, 6);
    data  = bytes.slice(6);
}

// 'map' replaces each element in the array as per a specified function
// (each element will be replaced with what is returned by the function)
// The passed function accepts the value and index of the element as its
// arguments
var decoded = data.map(function(byte, index) { // index === 0 for the first byte
    return byte ^ masks[ index % 4 ];          // of 'data', not of 'bytes'
    //         xor            mod
});

您还可以下载规范,这可能会很有帮助(因为它当然包含了您需要理解格式的一切内容)。


好的,星期一我会在工作中试一下。如果行得通的话,我会把你的回答标记为被采纳的答案... :) - gislikonrad
终于抽出时间来看一下我的WebSocket服务器的解决方案。效果非常好,谢谢你啊。 - gislikonrad
@Gísli Konráð:太好了,它对你起作用了;WebSockets并不是很适合调试。 - pimvdb
我认为你的代码不完整,当我测试系统时,从客户端websocket接收到以下字节,但无法解码: 136,128,44,185,105,136 第二个字节是128,在你的代码中不支持吗? - Ali Yousefi
你能澄清一下“第一个字节的最后四位将是0002”吗?如果你的二进制数字可以有一个值为2,那么你可能刚刚发明了一种新的数学类型 ;) - Niko O

12

这段C#代码对我来说运行良好。通过套接字将来自浏览器的文本数据解码到C#服务器。

    public static string GetDecodedData(byte[] buffer, int length)
    {
        byte b = buffer[1];
        int dataLength = 0;
        int totalLength = 0;
        int keyIndex = 0;

        if (b - 128 <= 125)
        {
            dataLength = b - 128;
            keyIndex = 2;
            totalLength = dataLength + 6;
        }

        if (b - 128 == 126)
        {
            dataLength = BitConverter.ToInt16(new byte[] { buffer[3], buffer[2] }, 0);
            keyIndex = 4;
            totalLength = dataLength + 8;
        }

        if (b - 128 == 127)
        {
            dataLength = (int)BitConverter.ToInt64(new byte[] { buffer[9], buffer[8], buffer[7], buffer[6], buffer[5], buffer[4], buffer[3], buffer[2] }, 0);
            keyIndex = 10;
            totalLength = dataLength + 14;
        }

        if (totalLength > length)
            throw new Exception("The buffer length is small than the data length");

        byte[] key = new byte[] { buffer[keyIndex], buffer[keyIndex + 1], buffer[keyIndex + 2], buffer[keyIndex + 3] };

        int dataIndex = keyIndex + 4;
        int count = 0;
        for (int i = dataIndex; i < totalLength; i++)
        {
            buffer[i] = (byte)(buffer[i] ^ key[count % 4]);
            count++;
        }

        return Encoding.ASCII.GetString(buffer, dataIndex, dataLength);
    }

根据问题(C#代码),应该将此标记为答案。 - cdie
totalLength是什么,为什么要将keyIndex + 4加到它上面? - David Gölzhäuser

2
更准确地说,Chrome已经从协议的Hixie-76版本转向了HyBi-10版本。 HyBi-08到HyBi-10都报告为版本8,因为真正改变的只是规范文本,而不是线路格式。
帧结构已经从使用'\x00...\xff'变成了每个帧都有一个2-7字节的头部,其中包含有效负载长度等信息。在规范的第4.2节中有一个帧格式的图表。还要注意,从客户端(浏览器)到服务器的数据是掩码的(客户端-服务器帧头的4个字节包含解除掩码的密钥)。
您可以查看 websockify,它是我创建的 WebSockets 到 TCP socket 代理/桥接程序,用于支持 noVNC。它是用 Python 实现的,但您应该能够从 encode_hybidecode_hybi 程序中获得想法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接