On GitHub there is a repository with implementation of decoding frames of the WebSocket protocol. The problem is that if many messages are sent at one point in time, the browser sticks together frames and the code does not work correctly.

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

enter image description here

Based on the names of variables, this happens because the code skips the header, and considers the rest of the information as the content of the frame. But since the message has two frames, the second header is not skipped.

 for (Int32 i = indexFirstDataByte, j = 0; i < bytes.Length; i++, j++) 
  • This is not a browser that sticks frames together, this is TCP protocol, it actually works ... - Pavel Mayorov
  • @PavelMayorov Well, the browser sends two frames together, without delay between them. According to the TCP protocol. Therefore, stream.DataAvailable does not switch to false and the bytes from the two frames are in the same array - Tripol Peter
  • You write as if frames sent with a delay can not stick together with each other! And they stick together at any network lag. - Pavel Mayorov
  • Delayed @PavelMayorov this code will not work, right. But this is a fix for the implementation, I needed to get real-time data from a web page running locally. - Tripolsky Peter

1 answer 1

The new code is more complicated. It receives an array of bytes, allocates the first frame from there, and returns the remaining bytes that are not involved in decoding.

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

It is used like this:

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

Just in case, I will attach an extension code that reads a block of information received from the browser

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

You can download the solution for Visual Studio via the link: https://cloud.mail.ru/public/KEeF/H81Q22MXP

  • Everything is too complicated. Why not just read the header from the stream first, and then the message of a known length? - Pavel Mayorov
  • @PavelMayorov real-time reading will definitely be more difficult. This code is a fix for the implementation, the link to which in question is Tripoli Peter
  • No, it will be easier. The code will remain the same, only the extra work with arrays and the ReadBlock method will disappear. - Pavel Mayorov
  • @PavelMayorov make the correct implementation and offer in the answers - Tripolsky Peter