private async Task HandleTextFrame(Header frameHeader, byte[] payload, int payloadOffset, int payloadCount) { if (frameHeader.IsFIN) { try { var text = Encoding.UTF8.GetString(payload, payloadOffset, payloadCount); await _module.OnSessionTextReceived(this, text); } catch (Exception ex) { await HandleUserSideError(ex); } } else { try { await _module.OnSessionFragmentationStreamOpened(this, payload, payloadOffset, payloadCount); } catch (Exception ex) { await HandleUserSideError(ex); } } }
private async Task Process() { try { while (State == WebSocketState.Open || State == WebSocketState.Closing) { int receiveCount = await _stream.ReadAsync(_receiveBuffer, _receiveBufferOffset, _receiveBuffer.Length - _receiveBufferOffset); if (receiveCount == 0) { break; } _keepAliveTracker.OnDataReceived(); BufferDeflector.ReplaceBuffer(_bufferManager, ref _receiveBuffer, ref _receiveBufferOffset, receiveCount); while (true) { Header frameHeader = null; if (_frameBuilder.TryDecodeFrameHeader(_receiveBuffer, _receiveBufferOffset, out frameHeader) && frameHeader.Length + frameHeader.PayloadLength <= _receiveBufferOffset) { try { if (!frameHeader.IsMasked) { await Close(WebSocketCloseCode.ProtocolError, "A server MUST close the connection upon receiving a frame that is not masked."); throw new WebSocketException(string.Format( "Server received unmasked frame [{0}] from remote [{1}].", frameHeader.OpCode, RemoteEndPoint)); } byte[] payload; int payloadOffset; int payloadCount; _frameBuilder.DecodePayload(_receiveBuffer, frameHeader, out payload, out payloadOffset, out payloadCount); switch (frameHeader.OpCode) { case OpCode.Continuation: { if (!frameHeader.IsFIN) { try { await _module.OnSessionFragmentationStreamContinued(this, payload, payloadOffset, payloadCount); } catch (Exception ex) { HandleUserSideError(ex); } } else { try { await _module.OnSessionFragmentationStreamClosed(this, payload, payloadOffset, payloadCount); } catch (Exception ex) { HandleUserSideError(ex); } } } break; case OpCode.Text: { if (frameHeader.IsFIN) { try { var text = Encoding.UTF8.GetString(payload, payloadOffset, payloadCount); await _module.OnSessionTextReceived(this, text); } catch (Exception ex) { HandleUserSideError(ex); } } else { try { await _module.OnSessionFragmentationStreamOpened(this, payload, payloadOffset, payloadCount); } catch (Exception ex) { HandleUserSideError(ex); } } } break; case OpCode.Binary: { if (frameHeader.IsFIN) { try { await _module.OnSessionBinaryReceived(this, payload, payloadOffset, payloadCount); } catch (Exception ex) { HandleUserSideError(ex); } } else { try { await _module.OnSessionFragmentationStreamOpened(this, payload, payloadOffset, payloadCount); } catch (Exception ex) { HandleUserSideError(ex); } } } break; case OpCode.Close: { if (!frameHeader.IsFIN) { throw new WebSocketException(string.Format( "Server received unfinished frame [{0}] from remote [{1}].", frameHeader.OpCode, RemoteEndPoint)); } if (payloadCount > 1) { var statusCode = payload[0] * 256 + payload[1]; var closeCode = (WebSocketCloseCode)statusCode; var closeReason = string.Empty; if (payloadCount > 2) { closeReason = Encoding.UTF8.GetString(payload, 2, payloadCount - 2); } #if DEBUG _log.DebugFormat("Session [{0}] received client side close frame [{1}] [{2}].", this, closeCode, closeReason); #endif // If an endpoint receives a Close frame and did not previously send a // Close frame, the endpoint MUST send a Close frame in response. (When // sending a Close frame in response, the endpoint typically echos the // status code it received.) It SHOULD do so as soon as practical. await Close(closeCode, closeReason); } else { #if DEBUG _log.DebugFormat("Session [{0}] received client side close frame but no status code.", this); #endif await Close(WebSocketCloseCode.InvalidPayloadData); } } break; case OpCode.Ping: { if (!frameHeader.IsFIN) { throw new WebSocketException(string.Format( "Server received unfinished frame [{0}] from remote [{1}].", frameHeader.OpCode, RemoteEndPoint)); } // Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in // response, unless it already received a Close frame. It SHOULD // respond with Pong frame as soon as is practical. Pong frames are // discussed in Section 5.5.3. // // An endpoint MAY send a Ping frame any time after the connection is // established and before the connection is closed. // // A Ping frame may serve either as a keep-alive or as a means to // verify that the remote endpoint is still responsive. var ping = Encoding.UTF8.GetString(payload, payloadOffset, payloadCount); #if DEBUG _log.DebugFormat("Session [{0}] received client side ping frame [{1}].", this, ping); #endif if (State == WebSocketState.Open) { // A Pong frame sent in response to a Ping frame must have identical // "Application data" as found in the message body of the Ping frame being replied to. var pong = new PongFrame(ping, false).ToArray(_frameBuilder); await SendFrame(pong); #if DEBUG _log.DebugFormat("Session [{0}] sends server side pong frame [{1}].", this, ping); #endif } } break; case OpCode.Pong: { if (!frameHeader.IsFIN) { throw new WebSocketException(string.Format( "Server received unfinished frame [{0}] from remote [{1}].", frameHeader.OpCode, RemoteEndPoint)); } // If an endpoint receives a Ping frame and has not yet sent Pong // frame(s) in response to previous Ping frame(s), the endpoint MAY // elect to send a Pong frame for only the most recently processed Ping frame. // // A Pong frame MAY be sent unsolicited. This serves as a // unidirectional heartbeat. A response to an unsolicited Pong frame is not expected. var pong = Encoding.UTF8.GetString(payload, payloadOffset, payloadCount); StopKeepAliveTimeoutTimer(); #if DEBUG _log.DebugFormat("Session [{0}] received client side pong frame [{1}].", this, pong); #endif } break; default: { // Incoming data MUST always be validated by both clients and servers. // If, at any time, an endpoint is faced with data that it does not // understand or that violates some criteria by which the endpoint // determines safety of input, or when the endpoint sees an opening // handshake that does not correspond to the values it is expecting // (e.g., incorrect path or origin in the client request), the endpoint // MAY drop the TCP connection. If the invalid data was received after // a successful WebSocket handshake, the endpoint SHOULD send a Close // frame with an appropriate status code (Section 7.4) before proceeding // to _Close the WebSocket Connection_. Use of a Close frame with an // appropriate status code can help in diagnosing the problem. If the // invalid data is sent during the WebSocket handshake, the server // SHOULD return an appropriate HTTP [RFC2616] status code. await Close(WebSocketCloseCode.InvalidMessageType); throw new NotSupportedException( string.Format("Not support received opcode [{0}].", (byte)frameHeader.OpCode)); } } } catch (Exception ex) { _log.Error(string.Format("Session [{0}] exception occurred, [{1}].", this, ex.Message), ex); throw; } finally { try { BufferDeflector.ShiftBuffer(_bufferManager, frameHeader.Length + frameHeader.PayloadLength, ref _receiveBuffer, ref _receiveBufferOffset); } catch (ArgumentOutOfRangeException) { } } } else { break; } } } } catch (Exception ex) when(!ShouldThrow(ex)) { } finally { await Abort(); } }