Как (де) создавать кадры данных в WebSockets hybi 08+?

Поскольку Chrome обновлен до версии v14, они перешли из версии три проекта в восьмая версия проекта.

У меня есть приложение для внутреннего чата, работающее в WebSocket, и хотя я получил новое рукопожатие, структура данных, видимо, также изменилась. Мой сервер WebSocket основан на Nugget.

У кого-нибудь есть WebSocket, работающий с восьмой версией черновика, и есть пример того, как формировать данные, отправляемые по проводу?

Ответ 1

(См. также: Как отправлять и получать сообщения WebSocket на стороне сервера?)


Это довольно легко, но важно понимать формат.

Первый байт почти всегда 1000 0001, где 1 означает "последний кадр", три 0 являются зарезервированными битами без какого-либо значения, а 0001 означает, что это текстовый фрейм (который Chrome отправляет с помощью метода ws.send()).

( Обновление: Теперь Chrome может также отправлять двоичные кадры с помощью ArrayBuffer. Последние четыре бита первого байта будут 0002, поэтому вы можете различать текст и двоичные данные. Декодирование данных работает точно так же.)

Второй байт содержит 1 (это означает, что он "замаскирован" (закодирован)), за которым следуют семь бит, которые представляют размер кадра. Если это между 000 0000 и 111 1101, то размер. Если он 111 1110, следующие 2 байта - это длина (потому что он не помещается в семь бит), а если он 111 1111, следующие 8 байтов - это длина (если он не будет вписываться в два байта либо).

Ниже приведены четыре байта, которые являются "масками", которые необходимо декодировать данные кадра. Это делается с использованием xor-кодирования, которое использует одну из масок, как определено indexOfByteInData mod 4 данных. Декодирование просто работает как encodedByte xor maskByte (где maskByte есть indexOfByteInData mod 4).

Теперь я должен сказать, что у меня вообще нет опыта с С#, но это какой-то псевдокод (какой-то 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
});

Вы также можете скачать спецификацию, которая может быть полезной (она, конечно, содержит все необходимое для понимания формата).

Ответ 2

Этот код С# отлично работает для меня. Декодируйте текстовые данные, поступающие из браузера на сервер С# через сокет.

    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);
    }

Ответ 3

Чтобы быть более точным, Chrome перешел от версии протокола Hixie-76 к HyBi-10 версия протокола. HyBi-08 через HyBi-10 все сообщают как версию 8, потому что это был только текст спецификации, который изменился, а не формат провода.

Кадрирование изменилось с использования '\ x00...\xff' на использование заголовка длиной 2-7 байт для каждого кадра, который содержит длину полезной нагрузки между прочим. Существует диаграмма формата кадра в разделе 4.2 спецификации. Также обратите внимание, что данные из клиента (браузера) на сервер маскируются (4 байта заголовков кадров клиент-сервер содержат ключ маскирования).

Вы можете посмотреть websockify, который является WebSockets для прокси-сервера/моста TCP, который я создал для поддержки noVNC. Он реализован в python, но вы должны получить эту идею из encode_hybi и decode_hybi.