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); } }
private void HandleConnection(IWebSocketClientContext client) { using (EneterTrace.Entering()) { string aClientIp = (client.ClientEndPoint != null) ? client.ClientEndPoint.ToString() : ""; TClientContext aClientContext = new TClientContext(client); string aClientId = null; try { client.SendTimeout = mySendTimeout; client.ReceiveTimeout = myReceiveTimeout; // If protocol formatter does not use OpenConnection message. if (!myProtocolUsesOpenConnectionMessage) { aClientId = Guid.NewGuid().ToString(); using (ThreadLock.Lock(myConnectedClients)) { myConnectedClients[aClientId] = aClientContext; } ProtocolMessage anOpenConnectionProtocolMessage = new ProtocolMessage(EProtocolMessageType.OpenConnectionRequest, aClientId, null); MessageContext aMessageContext = new MessageContext(anOpenConnectionProtocolMessage, aClientIp); NotifyMessageContext(aMessageContext); } while (true) { // Block until a message is received or the connection is closed. WebSocketMessage aWebSocketMessage = client.ReceiveMessage(); if (aWebSocketMessage != null && myMessageHandler != null) { ProtocolMessage aProtocolMessage = myProtocolFormatter.DecodeMessage((Stream)aWebSocketMessage.InputStream); // Note: security reasons ignore close connection message in WebSockets. // So that it is not possible that somebody will just send a close message which will have id of somebody else. // The connection will be closed when the client closes the socket. if (aProtocolMessage != null && aProtocolMessage.MessageType != EProtocolMessageType.CloseConnectionRequest) { MessageContext aMessageContext = new MessageContext(aProtocolMessage, aClientIp); // If protocol formatter uses open connection message to create the connection. if (aProtocolMessage.MessageType == EProtocolMessageType.OpenConnectionRequest && myProtocolUsesOpenConnectionMessage) { // Note: if client id is already set then it means this client has already open connection. if (string.IsNullOrEmpty(aClientId)) { aClientId = !string.IsNullOrEmpty(aProtocolMessage.ResponseReceiverId) ? aProtocolMessage.ResponseReceiverId : Guid.NewGuid().ToString(); using (ThreadLock.Lock(myConnectedClients)) { if (!myConnectedClients.ContainsKey(aClientId)) { myConnectedClients[aClientId] = aClientContext; } else { // Note: if the client id already exists then the connection cannot be open // and the connection with this client will be closed. EneterTrace.Warning(TracedObject + "could not open connection for client '" + aClientId + "' because the client with same id is already connected."); break; } } } else { EneterTrace.Warning(TracedObject + "the client '" + aClientId + "' has already open connection."); } } // Notify message. // Ensure that nobody will try to use id of somebody else. aMessageContext.ProtocolMessage.ResponseReceiverId = aClientId; NotifyMessageContext(aMessageContext); } else if (aProtocolMessage == null) { // Client disconnected. Or the client shall be disconnected because of incorrect message format. break; } } else { break; } } } finally { // Remove client from connected clients. if (aClientId != null) { using (ThreadLock.Lock(myConnectedClients)) { myConnectedClients.Remove(aClientId); } } // If the disconnection does not come from the service // and the client was successfuly connected then notify about the disconnection. if (!aClientContext.IsClosedFromService && aClientId != null) { ProtocolMessage aCloseProtocolMessage = new ProtocolMessage(EProtocolMessageType.CloseConnectionRequest, aClientId, null); MessageContext aMessageContext = new MessageContext(aCloseProtocolMessage, aClientIp); // Notify duplex input channel about the disconnection. NotifyMessageContext(aMessageContext); } client.CloseConnection(); } } }