На 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++)
Новый код сложнее. Он получает массив байт, выделяет от туда первый фрейм и возвращает оставшиеся байты, не участвующие в декодировании
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
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости