private async Task <MessageReceivedEventArgs> MessageReadAsync(ClientMetadata md) { using (MemoryStream stream = new MemoryStream()) { byte[] buffer = new byte[65536]; ArraySegment <byte> seg = new ArraySegment <byte>(buffer); while (true) { WebSocketReceiveResult result = await md.Ws.ReceiveAsync(seg, md.TokenSource.Token); if (result.CloseStatus != null || md.Ws.State != WebSocketState.Open || result.MessageType == WebSocketMessageType.Close || md.TokenSource.Token.IsCancellationRequested) { throw new WebSocketException("Websocket closed."); } if (result.Count > 0) { stream.Write(buffer, 0, result.Count); } if (result.EndOfMessage) { return(new MessageReceivedEventArgs(md.IpPort, stream.ToArray(), result.MessageType)); } } } }
private async Task <bool> MessageWriteAsync(ClientMetadata client, byte[] data, WebSocketMessageType messageType) { try { #region Send-Message // Cannot have two simultaneous SendAsync calls so use a // semaphore to block the second until the first has completed await client.SendAsyncLock.WaitAsync(client.KillToken.Token); try { await client.Ws.SendAsync(new ArraySegment <byte>(data, 0, data.Length), messageType, true, client.KillToken.Token); } finally { client.SendAsyncLock.Release(); } return(true); #endregion } catch (Exception e) { Log("*** MessageWriteAsync " + client.IpPort() + " disconnected due to exception " + e); return(false); } }
private async Task DataReceiver(ClientMetadata md) { string header = "[WatsonWsServer " + md.IpPort + "] "; Logger?.Invoke(header + "starting data receiver"); byte[] buffer = new byte[65536]; try { while (true) { MessageReceivedEventArgs msg = await MessageReadAsync(md, buffer).ConfigureAwait(false); if (msg != null) { if (EnableStatistics) { _Stats.IncrementReceivedMessages(); _Stats.AddReceivedBytes(msg.Data.Count); } if (msg.Data != null) { Task unawaited = Task.Run(() => MessageReceived?.Invoke(this, msg), md.TokenSource.Token); } else { await Task.Delay(10).ConfigureAwait(false); } } } } catch (TaskCanceledException) { // thrown when disposed } catch (OperationCanceledException) { // thrown when disposed } catch (WebSocketException) { // thrown by MessageReadAsync } catch (Exception e) { Logger?.Invoke(header + "exception: " + Environment.NewLine + e.ToString()); } finally { string ipPort = md.IpPort; ClientDisconnected?.Invoke(this, new ClientDisconnectedEventArgs(md.IpPort)); md.Ws.Dispose(); Logger?.Invoke(header + "disconnected"); _Clients.TryRemove(ipPort, out _); } }
private async Task DataReceiver(ClientMetadata client, CancellationToken?cancelToken = null) { var clientId = client.IpPort(); try { #region Wait-for-Data while (true) { cancelToken?.ThrowIfCancellationRequested(); byte[] data = await MessageReadAsync(client); if (data != null) { if (MessageReceived != null) { var _ = Task.Run(() => MessageReceived?.Invoke(client.IpPort(), data), CancellationToken.None); } } else { // no message available await Task.Delay(30, cancelToken.GetValueOrDefault()); } } #endregion } catch (OperationCanceledException oce) { Log("DataReceiver client " + clientId + " disconnected (canceled): " + oce.Message); } catch (WebSocketException wse) { Log("DataReceiver client " + clientId + " disconnected (websocket exception): " + wse.Message); } finally { if (RemoveClient(client)) { // must only fire disconnected event if the client was previously connected. Note that // multithreading gives multiple disconnection events from the socket, the reader and the writer ClientDisconnected?.Invoke(clientId); client.Ws.Dispose(); Log("DataReceiver client " + clientId + " disconnected (now " + _Clients.Count + " clients active)"); } } }
private async Task <MessageReceivedEventArgs> MessageReadAsync(ClientMetadata md) { string header = "[WatsonWsServer " + md.IpPort + "] "; using (MemoryStream stream = new MemoryStream()) { byte[] buffer = new byte[65536]; ArraySegment <byte> seg = new ArraySegment <byte>(buffer); while (true) { WebSocketReceiveResult result = await md.Ws.ReceiveAsync(seg, md.TokenSource.Token); /* * Console.WriteLine("Websocket state : " + md.Ws.State); * Console.WriteLine("Close status : " + result.CloseStatus); * Console.WriteLine("Message type : " + result.MessageType); */ if (result.CloseStatus != null) { Logger?.Invoke(header + "close received"); await md.Ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); throw new WebSocketException("Websocket closed."); } if (md.Ws.State != WebSocketState.Open) { Logger?.Invoke(header + "websocket no longer open"); throw new WebSocketException("Websocket closed."); } if (md.TokenSource.Token.IsCancellationRequested) { Logger?.Invoke(header + "cancel requested"); } if (result.Count > 0) { stream.Write(buffer, 0, result.Count); } if (result.EndOfMessage) { return(new MessageReceivedEventArgs(md.IpPort, stream.ToArray(), result.MessageType)); } } } }
private async Task DataReceiver(ClientMetadata md) { string header = "[WatsonWsServer " + md.IpPort + "] "; Logger?.Invoke(header + "starting data receiver"); try { while (true) { MessageReceivedEventArgs msg = await MessageReadAsync(md); if (msg != null) { _Stats.IncrementReceivedMessages(); _Stats.AddReceivedBytes(msg.Data.Length); if (msg.Data != null) { MessageReceived?.Invoke(this, msg); } else { await Task.Delay(100); } } } } catch (OperationCanceledException) { // thrown when disposed } catch (WebSocketException) { // thrown by MessageReadAsync } catch (Exception e) { Logger?.Invoke(header + "exception: " + Environment.NewLine + e.ToString()); } finally { string ipPort = md.IpPort; ClientDisconnected?.Invoke(this, new ClientDisconnectedEventArgs(md.IpPort)); md.Ws.Dispose(); Logger?.Invoke(header + "disconnected"); _Clients.TryRemove(ipPort, out _); } }
private async Task <bool> MessageWriteAsync(ClientMetadata md, byte[] data, WebSocketMessageType messageType) { string header = "[WatsonWsServer.MessageWriteAsync " + md.IpPort + "] "; try { #region Send-Message // Cannot have two simultaneous SendAsync calls so use a // semaphore to block the second until the first has completed await md.SendLock.WaitAsync(md.TokenSource.Token); try { await md.Ws.SendAsync(new ArraySegment <byte>(data, 0, data.Length), messageType, true, md.TokenSource.Token); } finally { md.SendLock.Release(); } _Stats.SentMessages += 1; _Stats.SentBytes += data.Length; return(true); #endregion } catch (OperationCanceledException oce) { Logger?.Invoke(header + "disconnected (canceled): " + oce.Message); } catch (WebSocketException wse) { Logger?.Invoke(header + "disconnected (websocket exception): " + wse.Message); } catch (Exception e) { Logger?.Invoke(header + "disconnected due to exception: " + Environment.NewLine + e.ToString()); } return(false); }
private async Task <MessageReceivedEventArgs> MessageReadAsync(ClientMetadata md, byte[] buffer) { string header = "[WatsonWsServer " + md.IpPort + "] "; using (MemoryStream ms = new MemoryStream()) { ArraySegment <byte> seg = new ArraySegment <byte>(buffer); while (true) { WebSocketReceiveResult result = await md.Ws.ReceiveAsync(seg, md.TokenSource.Token).ConfigureAwait(false); if (result.CloseStatus != null) { Logger?.Invoke(header + "close received"); await md.Ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); throw new WebSocketException("Websocket closed."); } if (md.Ws.State != WebSocketState.Open) { Logger?.Invoke(header + "websocket no longer open"); throw new WebSocketException("Websocket closed."); } if (md.TokenSource.Token.IsCancellationRequested) { Logger?.Invoke(header + "cancel requested"); } if (result.Count > 0) { ms.Write(buffer, 0, result.Count); } if (result.EndOfMessage) { return(new MessageReceivedEventArgs(md.IpPort, new ArraySegment <byte>(ms.GetBuffer(), 0, (int)ms.Length), result.MessageType)); } } } }
private async Task AcceptConnections() { string header = "[WatsonWsServer.AcceptConnections] "; try { _Listener.Start(); while (!_Token.IsCancellationRequested) { HttpListenerContext ctx = await _Listener.GetContextAsync(); string ip = ctx.Request.RemoteEndPoint.Address.ToString(); int port = ctx.Request.RemoteEndPoint.Port; string ipPort = ip + ":" + port; lock (_PermittedIpsLock) { if (PermittedIpAddresses != null && PermittedIpAddresses.Count > 0 && !PermittedIpAddresses.Contains(ip)) { Logger?.Invoke(header + "rejecting connection from " + ipPort + " (not permitted)"); ctx.Response.StatusCode = 401; ctx.Response.Close(); continue; } } if (!ctx.Request.IsWebSocketRequest) { if (HttpHandler != null) { Logger?.Invoke(header + "non-websocket request forwarded to HTTP handler from " + ipPort + ": " + ctx.Request.HttpMethod.ToString() + " " + ctx.Request.RawUrl); HttpHandler.Invoke(ctx); } else { Logger?.Invoke(header + "non-websocket request rejected from " + ipPort); ctx.Response.StatusCode = 400; ctx.Response.Close(); } continue; } CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; await Task.Run(() => { Logger?.Invoke(header + "starting data receiver for " + ipPort); Task.Run(async() => { WebSocketContext wsContext; try { wsContext = await ctx.AcceptWebSocketAsync(subProtocol: null); } catch (Exception) { Logger?.Invoke(header + "unable to retrieve websocket content for client " + ipPort); ctx.Response.StatusCode = 500; ctx.Response.Close(); return; } WebSocket ws = wsContext.WebSocket; ClientMetadata md = new ClientMetadata(ctx, ws, wsContext, tokenSource); _Clients.TryAdd(md.IpPort, md); ClientConnected?.Invoke(this, new ClientConnectedEventArgs(md.IpPort, ctx.Request)); await Task.Run(() => DataReceiver(md), token); }, token); }, _Token); } } catch (HttpListenerException) { // can be thrown when disposed } catch (OperationCanceledException) { // thrown when disposed } catch (Exception e) { Logger?.Invoke(header + "exception:" + Environment.NewLine + e.ToString()); } finally { ServerStopped?.Invoke(this, EventArgs.Empty); } }
private async Task <byte[]> MessageReadAsync(ClientMetadata client) { /* * * Do not catch exceptions, let them get caught by the data reader * to destroy the connection * */ #region Check-for-Null-Values if (client.HttpContext == null) { return(null); } if (client.WsContext == null) { return(null); } #endregion #region Variables byte[] contentBytes; #endregion #region Read-Data using (var dataStream = new MemoryStream()) { const long bufferSize = 16 * 1024; var buffer = new byte[bufferSize]; var bufferSegment = new ArraySegment <byte>(buffer); while (client.Ws.State == WebSocketState.Open) { var receiveResult = await client.Ws.ReceiveAsync(bufferSegment, client.KillToken.Token); if (receiveResult.MessageType == WebSocketMessageType.Close) { throw new WebSocketException("Socket closed"); } // write this chunk to the data stream dataStream.Write(buffer, 0, receiveResult.Count); if (receiveResult.EndOfMessage) { // end of message so return the buffer break; } } contentBytes = dataStream.ToArray(); } #endregion return(contentBytes.Length == 0 ? null : contentBytes); }
private bool RemoveClient(ClientMetadata client) { return(_Clients.TryRemove(client.IpPort(), out var removedClient)); }
private bool AddClient(ClientMetadata client) { _Clients.TryRemove(client.IpPort(), out var removedClient); return(_Clients.TryAdd(client.IpPort(), client)); }
private async Task AcceptConnections() { try { #region Accept-WS-Connections _Listener.Start(); while (!_Token.IsCancellationRequested) { #region Accept-and-Verify-Connection HttpListenerContext ctx = await _Listener.GetContextAsync(); string clientIp = ctx.Request.RemoteEndPoint.Address.ToString(); int clientPort = ctx.Request.RemoteEndPoint.Port; string ipPort = clientIp + ":" + clientPort; lock (_PermittedIpsLock) { if (PermittedIpAddresses != null && PermittedIpAddresses.Count > 0 && !PermittedIpAddresses.Contains(clientIp)) { Log("*** AcceptConnections rejecting connection from " + clientIp + " (not permitted)"); ctx.Response.StatusCode = 401; ctx.Response.Close(); continue; } } Log("AcceptConnections accepted connection from " + clientIp + ":" + clientPort); if (!ctx.Request.IsWebSocketRequest) { Log("*** AcceptConnections rejecting connection from " + clientIp + " (not a websocket request)"); ctx.Response.StatusCode = 400; ctx.Response.Close(); continue; } #endregion #region Start-Client-Task CancellationTokenSource killTs = new CancellationTokenSource(); CancellationToken killToken = killTs.Token; Task _ = Task.Run(() => { Log("AcceptConnections starting data receiver for " + ipPort + " (now " + _Clients.Count + " clients)"); Task.Run(async() => { #region Connection-Callback if (ClientConnected != null) { bool connected = await ClientConnected(ipPort, ctx.Request); if (!connected) { Log("*** AcceptConnections unable to validate client " + ipPort); ctx.Response.StatusCode = 401; ctx.Response.Close(); return; } } #endregion #region Get-Websocket-Context WebSocketContext wsContext; try { wsContext = await ctx.AcceptWebSocketAsync(subProtocol: null); } catch (Exception) { Log("*** AcceptConnections unable to retrieve websocket content for client " + ipPort); ctx.Response.StatusCode = 500; ctx.Response.Close(); return; } WebSocket ws = wsContext.WebSocket; ClientMetadata client = new ClientMetadata(ctx, ws, wsContext, killTs); #endregion #region Add-Client if (!AddClient(client)) { Log("*** AcceptConnections unable to add client " + ipPort); ctx.Response.StatusCode = 500; ctx.Response.Close(); return; } await DataReceiver(client, killToken); #endregion }, killToken); }, _Token); #endregion } #endregion } catch (OperationCanceledException) { } catch (Exception e) { LogException("AcceptConnections", e); } finally { ServerStopped?.Invoke(); } }
private async Task AcceptConnections() { try { _Listener.Start(); while (true) { if (_Token.IsCancellationRequested) { break; } if (!_Listener.IsListening) { Task.Delay(100).Wait(); continue; } HttpListenerContext ctx = await _Listener.GetContextAsync(); string ip = ctx.Request.RemoteEndPoint.Address.ToString(); int port = ctx.Request.RemoteEndPoint.Port; string ipPort = ip + ":" + port; lock (_PermittedIpsLock) { if (PermittedIpAddresses != null && PermittedIpAddresses.Count > 0 && !PermittedIpAddresses.Contains(ip)) { Logger?.Invoke(_Header + "rejecting " + ipPort + " (not permitted)"); ctx.Response.StatusCode = 401; ctx.Response.Close(); continue; } } if (!ctx.Request.IsWebSocketRequest) { if (HttpHandler == null) { Logger?.Invoke(_Header + "non-websocket request rejected from " + ipPort); ctx.Response.StatusCode = 400; ctx.Response.Close(); } else { Logger?.Invoke(_Header + "non-websocket request from " + ipPort + " HTTP-forwarded: " + ctx.Request.HttpMethod.ToString() + " " + ctx.Request.RawUrl); HttpHandler.Invoke(ctx); } continue; } else { /* * HttpListenerRequest req = ctx.Request; * Console.WriteLine(Environment.NewLine + req.HttpMethod.ToString() + " " + req.RawUrl); * if (req.Headers != null && req.Headers.Count > 0) * { * Console.WriteLine("Headers:"); * var items = req.Headers.AllKeys.SelectMany(req.Headers.GetValues, (k, v) => new { key = k, value = v }); * foreach (var item in items) * { * Console.WriteLine(" {0}: {1}", item.key, item.value); * } * } */ } await Task.Run(() => { Logger?.Invoke(_Header + "starting data receiver for " + ipPort); CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; Task.Run(async() => { WebSocketContext wsContext = await ctx.AcceptWebSocketAsync(subProtocol: null); WebSocket ws = wsContext.WebSocket; ClientMetadata md = new ClientMetadata(ctx, ws, wsContext, tokenSource); _Clients.TryAdd(md.IpPort, md); ClientConnected?.Invoke(this, new ClientConnectedEventArgs(md.IpPort, ctx.Request)); await Task.Run(() => DataReceiver(md), token); }, token); }, _Token); } } catch (HttpListenerException) { // thrown when disposed } catch (OperationCanceledException) { // thrown when disposed } catch (ObjectDisposedException) { // thrown when disposed } catch (Exception e) { Logger?.Invoke(_Header + "listener exception:" + Environment.NewLine + e.ToString()); } finally { ServerStopped?.Invoke(this, EventArgs.Empty); } }
private async Task <bool> MessageWriteAsync(ClientMetadata md, byte[] data, WebSocketMessageType msgType, CancellationToken token) { string header = "[WatsonWsServer " + md.IpPort + "] "; CancellationToken[] tokens = new CancellationToken[3]; tokens[0] = _Token; tokens[1] = token; tokens[2] = md.TokenSource.Token; using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(tokens)) { try { #region Send-Message await md.SendLock.WaitAsync(md.TokenSource.Token).ConfigureAwait(false); try { await md.Ws.SendAsync(new ArraySegment <byte>(data, 0, data.Length), msgType, true, linkedCts.Token).ConfigureAwait(false); } finally { md.SendLock.Release(); } _Stats.IncrementSentMessages(); _Stats.AddSentBytes(data.Length); return(true); #endregion } catch (TaskCanceledException) { if (_Token.IsCancellationRequested) { Logger?.Invoke(header + "server canceled"); } else if (token.IsCancellationRequested) { Logger?.Invoke(header + "message send canceled"); } else if (md.TokenSource.Token.IsCancellationRequested) { Logger?.Invoke(header + "client canceled"); } } catch (OperationCanceledException) { if (_Token.IsCancellationRequested) { Logger?.Invoke(header + "canceled"); } else if (token.IsCancellationRequested) { Logger?.Invoke(header + "message send canceled"); } else if (md.TokenSource.Token.IsCancellationRequested) { Logger?.Invoke(header + "client canceled"); } } catch (ObjectDisposedException) { Logger?.Invoke(header + "disposed"); } catch (WebSocketException) { Logger?.Invoke(header + "websocket disconnected"); } catch (SocketException) { Logger?.Invoke(header + "socket disconnected"); } catch (InvalidOperationException) { Logger?.Invoke(header + "disconnected due to invalid operation"); } catch (IOException) { Logger?.Invoke(header + "IO disconnected"); } catch (Exception e) { Logger?.Invoke(header + "exception: " + Environment.NewLine + e.ToString()); } } return(false); }
private async Task AcceptConnections() { try { #region Accept-WS-Connections Listener.Start(); while (!Token.IsCancellationRequested) { #region Accept-Connection HttpListenerContext httpContext = await Listener.GetContextAsync(); #endregion if (httpContext.Request.RemoteEndPoint != null) { #region Check-IP-Address string clientIp = httpContext.Request.RemoteEndPoint.Address.ToString(); int clientPort = httpContext.Request.RemoteEndPoint.Port; Dictionary <string, string> query = new Dictionary <string, string>(); NameValueCollection requestQuery = httpContext.Request.QueryString; foreach (string key in requestQuery.AllKeys) { query[key] = requestQuery[key]; } if (PermittedIps != null && PermittedIps.Count > 0) { if (!PermittedIps.ContainsKey(clientIp)) { Log("*** AcceptConnections rejecting connection from " + clientIp + " (not permitted)"); httpContext.Response.StatusCode = 401; httpContext.Response.Close(); return; } } Log("AcceptConnections accepted connection from " + clientIp + ":" + clientPort); #endregion #region Get-Websocket-Context WebSocketContext wsContext = null; try { wsContext = httpContext.AcceptWebSocketAsync(subProtocol: null).Result; } catch (Exception) { Log("*** AcceptConnections unable to retrieve websocket content for client " + clientIp + ":" + clientPort); httpContext.Response.StatusCode = 500; httpContext.Response.Close(); return; } WebSocket ws = wsContext.WebSocket; #endregion Task unawaited = Task.Run(() => { #region Add-to-Client-List // Do not decrement in this block, decrement is done by the connection reader ClientMetadata currClient = new ClientMetadata(httpContext, ws, wsContext); if (!AddClient(currClient)) { Log("*** AcceptConnections unable to add client " + clientIp + ":" + clientPort); httpContext.Response.StatusCode = 500; httpContext.Response.Close(); return; } #endregion #region Start-Data-Receiver CancellationToken killToken = currClient.KillToken.Token; Log("AcceptConnections starting data receiver for " + clientIp + ":" + clientPort + " (now " + Clients.Count + " clients)"); if (ClientConnected != null) { Task.Run(() => ClientConnected(clientIp + ":" + clientPort, query), killToken); } Task.Run(async() => await DataReceiver(currClient, killToken), killToken); #endregion }, Token); } } #endregion } catch (Exception e) { LogException("AcceptConnections", e); } }