Реализация протокола WebSocket на C#

261
05 сентября 2018, 09:20

На GitHub есть репозиторий с реализацией декодирования фреймов протокола WebSocket. Проблема в том, что если в один момент времени отправляется много сообщений, браузер склеивает фреймы и код работает некоректно

private String DecodeMessageFromClient(Byte[] bytes)
{
    try
    {
        String incomingData = String.Empty;
        Byte secondByte = bytes[1];
        Int32 dataLength = secondByte & 127;
        Int32 indexFirstMask = 2;
        if (dataLength == 126) indexFirstMask = 4;
        else if (dataLength == 127) indexFirstMask = 10;
        IEnumerable<Byte> keys = bytes.Skip(indexFirstMask).Take(4);
        Int32 indexFirstDataByte = indexFirstMask + 4;
        Byte[] decoded = new Byte[bytes.Length - indexFirstDataByte];
        for (Int32 i = indexFirstDataByte, j = 0; i < bytes.Length; i++, j++)
        {
            decoded[j] = (Byte)(bytes[i] ^ keys.ElementAt(j % 4));
        }
        return incomingData = Encoding.UTF8.GetString(decoded, 0, decoded.Length);
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Could not decode due to :" + ex.Message);
    }
    return null;
}  

Исходя из названий переменных, происходит это по тому, что код пропускает заголовок, а остальную информацию считает за содержимое фрейма. Но так как в сообщении два фрейма, второй заголовок не пропускается.

for (Int32 i = indexFirstDataByte, j = 0; i < bytes.Length; i++, j++)
Answer 1

Новый код сложнее. Он получает массив байт, выделяет от туда первый фрейм и возвращает оставшиеся байты, не участвующие в декодировании

public static byte[] WebSocketBackendMessage(this NetworkStream stream,byte[] bytes,out string message,out WebSocketMessageType type)
{
    byte oppcode = bytes[0];
    byte shift = 15;
    type = (WebSocketMessageType)(oppcode & shift);
    String incomingData = String.Empty;
    Byte secondByte = bytes[1];
    Int32 dataLength = secondByte & 127;
    Int32 indexFirstMask = 2;
    if (dataLength == 126)
    {
        indexFirstMask = 4;
        byte[] newlenght = bytes.Skip(2).Take(2).ToArray();
        Array.Reverse(newlenght);
        dataLength = BitConverter.ToUInt16(newlenght, 0);
    }
    else if (dataLength == 127)
    {
        indexFirstMask = 10;
        throw new Exception("Слишком большой массив информации был отправлен на вебсокет!");
    }
    IEnumerable<Byte> keys = bytes.Skip(indexFirstMask).Take(4);
    Int32 indexFirstDataByte = indexFirstMask + 4;
    Byte[] decoded = new Byte[bytes.Length - indexFirstDataByte];
    for (Int32 i = indexFirstDataByte, j = 0; i < indexFirstDataByte + dataLength; i++, j++)
    {
        decoded[j] = (Byte)(bytes[i] ^ keys.ElementAt(j % 4));
    }
    message = Encoding.UTF8.GetString(decoded, 0, decoded.Length);
    byte[] remain = bytes.Skip(indexFirstDataByte + dataLength).ToArray();
    if (remain.Length == 0) return null;
    else return remain;
}

Используется он так:

byte[] block = stream.ReadBlock();
string message = null;
WebSocketMessageType type;
bool lastFrameEmpty = false;
while(!lastFrameEmpty)
{
    lastFrameEmpty = (block = stream.WebSocketBackendMessage(block, out message,out type)) == null;
    if (type == WebSocketMessageType.Text)
    {
        Console.WriteLine("MESSAGE: {0}", message);
    }
    if (type == WebSocketMessageType.Close)
    {
        continueWorking = false;
        break;
    }
}

На всякий случай, прикреплю код расширения, считывающего блок информации, полученный от браузера

public static byte[] ReadBlock(this NetworkStream stream)
{
    Byte[] bytes = null;
    using (MemoryStream tmp = new MemoryStream())
    {
        byte[] data = new byte[1];
        int readed = -1;
        while (stream.DataAvailable)
        {
            readed = stream.Read(data, 0, data.Length);
            tmp.Write(data, 0, readed);
        }
        if (readed > -1)
        {
            bytes = tmp.ToArray();
        }
    }
    return bytes;
}

Вы можете скачать решение для Visual Studio по ссылке: https://cloud.mail.ru/public/KEeF/H81Q22MXP

READ ALSO
laravel авторизация роли и прав

laravel авторизация роли и прав

я составляю cms на laravel c возможность расширяться через плагиныВозник вопрос, у меня есть пользователи и каждому можно задавать роли админ...

243
American Express Api для интернет магазина

American Express Api для интернет магазина

Подскажите где взять номальную документацию по роботе с АПИ для оплаты по American Express? И существует ли оно вообще? Или это АПИ закрытое? Можете...

249
Изменение массива php

Изменение массива php

Есть массив

238
Как сделать кнопку назад в Laravel Blade?

Как сделать кнопку назад в Laravel Blade?

Нашел ответ 3 летней давности где предлагается использовать

237