public void DoRequestListening() { using (EneterTrace.Entering()) { myIsListeningToResponses = true; ushort aCloseCode = 0; try { DynamicStream aContinuousMessageStream = null; while (!myStopReceivingRequestedFlag) { // Decode the incoming message. WebSocketFrame aFrame = WebSocketFormatter.DecodeFrame(myClientStream); if (!myStopReceivingRequestedFlag && aFrame != null) { // Frames from server must be unmasked. // According the protocol, If the frame was NOT masked, the server must close connection with the client. if (aFrame.MaskFlag == false) { throw new InvalidOperationException(TracedObject + "received unmasked frame from the client. Frames from client shall be masked."); } // Process the frame. if (aFrame.FrameType == EFrameType.Ping) { // Response 'pong'. The response responses same data as received in the 'ping'. SendFrame(maskingKey => WebSocketFormatter.EncodePongFrame(maskingKey, aFrame.Message)); } else if (aFrame.FrameType == EFrameType.Close) { EneterTrace.Debug(TracedObject + "received the close frame."); break; } else if (aFrame.FrameType == EFrameType.Pong) { Notify(PongReceived); } // If a new message starts. else if (aFrame.FrameType == EFrameType.Binary || aFrame.FrameType == EFrameType.Text) { // If a previous message is not finished then the new message is not expected -> protocol error. if (aContinuousMessageStream != null) { EneterTrace.Warning(TracedObject + "detected unexpected new message. (previous message was not finished)"); // Protocol error close code. aCloseCode = 1002; break; } WebSocketMessage aReceivedMessage = null; // If the message does not come in multiple frames then optimize the performance // and use MemoryStream instead of DynamicStream. if (aFrame.IsFinal) { MemoryStream aMessageStream = new MemoryStream(aFrame.Message); aReceivedMessage = new WebSocketMessage(aFrame.FrameType == EFrameType.Text, aMessageStream); } else // if the message is split to several frames then use DynamicStream so that writing of incoming // frames and reading of already received data can run in parallel. { // Create stream where the message data will be writen. aContinuousMessageStream = new DynamicStream(); aContinuousMessageStream.WriteWithoutCopying(aFrame.Message, 0, aFrame.Message.Length); aReceivedMessage = new WebSocketMessage(aFrame.FrameType == EFrameType.Text, aContinuousMessageStream); } // Put received message to the queue. myReceivedMessages.EnqueueMessage(aReceivedMessage); } // If a message continues. (I.e. message is split into more fragments.) else if (aFrame.FrameType == EFrameType.Continuation) { // If the message does not exist then continuing frame does not have any sense -> protocol error. if (aContinuousMessageStream == null) { EneterTrace.Warning(TracedObject + "detected unexpected continuing of a message. (none message was started before)"); // Protocol error close code. aCloseCode = 1002; break; } aContinuousMessageStream.WriteWithoutCopying(aFrame.Message, 0, aFrame.Message.Length); // If this is the final frame. if (aFrame.IsFinal) { aContinuousMessageStream.IsBlockingMode = false; aContinuousMessageStream = null; } } } // If disconnected if (aFrame == null)// || !myTcpClient.Client.Poll(0, SelectMode.SelectWrite)) { //EneterTrace.Warning(TracedObject + "detected the TCP connection is not available. The connection will be closed."); break; } } } catch (IOException) { // Ignore this exception. It is often thrown when the connection was closed. // Do not thrace this because the tracing degradates the performance in this case. } catch (Exception err) { EneterTrace.Error(TracedObject + ErrorHandler.FailedInListeningLoop, err); } // If the connection is being closed due to a protocol error. if (aCloseCode > 1000) { // Try to send the close message. try { byte[] aCloseMessage = WebSocketFormatter.EncodeCloseFrame(null, aCloseCode); myClientStream.Write(aCloseMessage, 0, aCloseMessage.Length); } catch { } } myIsListeningToResponses = false; myReceivedMessages.UnblockProcessingThreads(); // Notify the listening to messages stoped. Notify(ConnectionClosed); } }
public static WebSocketFrame DecodeFrame(Stream inputStream) { using (EneterTrace.Entering()) { try { // Note: Do not enclose the BinaryReader with using because it will close the stream!!! BinaryReader aReader = new BinaryReader(inputStream); // Read the first 2 bytes. byte[] aFirst2Bytes = aReader.ReadBytes(2); if (aFirst2Bytes.Length < 2) { throw new EndOfStreamException("End of stream during reading first two bytes of web socket frame."); } // Get if final. bool anIsFinal = (aFirst2Bytes[0] & 0x80) != 0; // Get opcode. EFrameType aFrameType = (EFrameType)(aFirst2Bytes[0] & 0xF); // Get if masked. bool anIsMasked = (aFirst2Bytes[1] & 0x80) != 0; // Get the message length. int aMessageLength = aFirst2Bytes[1] & 0x7F; if (aMessageLength == 126) { // The length is encoded in next 2 bytes (16 bits). ushort aLength = aReader.ReadUInt16(); // Websockets are in big endians, so convert it to little endian. aMessageLength = ConvertEndian(aLength); } else if (aMessageLength == 127) { // The length is encoded in next 8 bytes (64 bits). ulong aLength = aReader.ReadUInt64(); // Websockets are in big endians, so convert it to little endian. aLength = ConvertEndian(aLength); aMessageLength = (int)aLength; } // Get mask bytes. byte[] aMaskBytes = null; if (anIsMasked) { aMaskBytes = aReader.ReadBytes(4); if (aMaskBytes.Length < 4) { throw new EndOfStreamException("End of stream during reading web socket mask bytes."); } } // Get the message data. byte[] aMessageData = aReader.ReadBytes(aMessageLength); if (aMessageData.Length < aMessageLength) { throw new EndOfStreamException("End of stream during reading message data from the web socket frame."); } // If mask was used then unmask data. if (anIsMasked) { for (int i = 0; i < aMessageData.Length; ++i) { aMessageData[i] = (byte)(aMessageData[i] ^ aMaskBytes[i % 4]); } } WebSocketFrame aFrame = new WebSocketFrame(aFrameType, anIsMasked, aMessageData, anIsFinal); return(aFrame); } catch (EndOfStreamException) { // End of the stream. return(null); } catch (ObjectDisposedException) { // End of the stream. return(null); } } }