/// Called when a Close frame is received, sends a response close frame if applicable private WebSocketReceiveResult RespondToCloseFrame(WebSocketFrame frame, ArraySegment <byte> buffer) { _closeStatus = frame.CloseStatus; _closeStatusDescription = frame.CloseStatusDescription; if (_state == WebSocketState.CloseSent) { // this is a response to close handshake initiated by this instance _state = WebSocketState.Closed; } else if (_state == WebSocketState.Open) { // do not echo the close payload back to the client, there is no requirement for it in the spec. // However, the same CloseStatus as recieved should be sent back. ArraySegment <byte> closePayload = new ArraySegment <byte>(new byte[0], 0, 0); _state = WebSocketState.CloseReceived; using (MemoryStream stream = _recycledStreamFactory()) { WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, closePayload, stream, true, _isClient); WriteStreamToNetwork(stream); } } return(new WebSocketReceiveResult(frame.Count, WebSocketMessageType.Close, frame.IsFinBitSet, frame.CloseStatus, frame.CloseStatusDescription)); }
/// NOTE: pong payload must be 125 bytes or less /// Pong should contain the same payload as the ping private void SendPong(ArraySegment <byte> payload) { // as per websocket spec if (payload.Count > MAX_PING_PONG_PAYLOAD_LEN) { Exception ex = new InvalidOperationException($"Max ping message size {MAX_PING_PONG_PAYLOAD_LEN} exceeded: {payload.Count}"); CloseOutputAutoTimeout(WebSocketCloseStatus.ProtocolError, ex.Message, ex); throw ex; } try { if (_state == WebSocketState.Open) { using (MemoryStream stream = _recycledStreamFactory()) { WebSocketFrameWriter.Write(WebSocketOpCode.Pong, payload, stream, true, _isClient); _logger(LogLevel.Debug, $"websocket.Pong: {payload.Count}"); WriteStreamToNetwork(stream); } } } catch (Exception ex) { CloseOutputAutoTimeout(WebSocketCloseStatus.EndpointUnavailable, "Unable to send Pong response", ex); throw; } }
/// Send data to the web socket public void Send(ArraySegment <byte> buffer, WebSocketMessageType messageType, bool endOfMessage) { using (MemoryStream stream = _recycledStreamFactory()) { WebSocketOpCode opCode = GetOppCode(messageType); if (_usePerMessageDeflate) { // NOTE: Compression is currently work in progress and should NOT be used in this library. // The code below is very inefficient for small messages. Ideally we would like to have some sort of moving window // of data to get the best compression. And we don't want to create new buffers which is bad for GC. using (MemoryStream temp = new MemoryStream()) { DeflateStream deflateStream = new DeflateStream(temp, CompressionMode.Compress); deflateStream.Write(buffer.Array, buffer.Offset, buffer.Count); deflateStream.Flush(); var compressedBuffer = new ArraySegment <byte>(temp.ToArray()); WebSocketFrameWriter.Write(opCode, compressedBuffer, stream, endOfMessage, _isClient); _logger(LogLevel.Debug, $"websocket.SendingFrame: {opCode}, {endOfMessage}, {compressedBuffer.Count}, compressed"); } } else { WebSocketFrameWriter.Write(opCode, buffer, stream, endOfMessage, _isClient); _logger(LogLevel.Debug, $"websocket.SendingFrame: {opCode}, {endOfMessage}, {buffer.Count}, uncompressed"); } WriteStreamToNetwork(stream); _isContinuationFrame = !endOfMessage; // TODO: is this correct?? } }
/// Fire and forget close public void CloseOutput(WebSocketCloseStatus closeStatus, string statusDescription) { if (_state == WebSocketState.Open) { _state = WebSocketState.Closed; // set this before we write to the network because the write may fail using (MemoryStream stream = _recycledStreamFactory()) { ArraySegment <byte> buffer = BuildClosePayload(closeStatus, statusDescription); WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, buffer, stream, true, _isClient); WriteStreamToNetwork(stream); } } }
/// Polite close (use the close handshake) public void Close(WebSocketCloseStatus closeStatus, string statusDescription) { if (_state == WebSocketState.Open) { using (MemoryStream stream = _recycledStreamFactory()) { ArraySegment <byte> buffer = BuildClosePayload(closeStatus, statusDescription); WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, buffer, stream, true, _isClient); _logger(LogLevel.Debug, $"websocket.Close: {closeStatus}, {statusDescription}"); WriteStreamToNetwork(stream); _state = WebSocketState.CloseSent; } } else { _logger(LogLevel.Warning, $"websocket already closed"); } }
/// Call this automatically from server side each keepAliveInterval period /// NOTE: ping payload must be 125 bytes or less public void SendPing() { _pingCounter++; _pingPayload = BitConverter.GetBytes(_pingCounter); var payload = new ArraySegment <byte>(_pingPayload); if (payload.Count > MAX_PING_PONG_PAYLOAD_LEN) { throw new InvalidOperationException($"Cannot send Ping: Max ping message size {MAX_PING_PONG_PAYLOAD_LEN} exceeded: {payload.Count}"); } if (_state == WebSocketState.Open) { using (MemoryStream stream = _recycledStreamFactory()) { NeedsPing = false; WebSocketFrameWriter.Write(WebSocketOpCode.Ping, payload, stream, true, _isClient); _logger(LogLevel.Debug, $"websocket.Ping: {payload.Count}"); WriteStreamToNetwork(stream); } } }