private void _ClientReadLine(ConnectedEndPoint readClient, string text) { _OnStatus($"Client {readClient.RemoteEndPoint}: \"{text}\""); lock (_lock) { if (_closing) { return; } text = $"{readClient.RemoteEndPoint}: {text}"; foreach (ConnectedEndPoint client in _clients.Where(c => c != readClient)) { try { client.WriteLine(text); } catch (IOException e) { _OnClientException(client, e.Message); } } } }
// Top-level "clean-up" method, which will observe and report all exceptions // In real-world code, would probably want to simply log any unexpected exceptions // to a log file and then exit the process. Here, we just exit after reporting // exception info to caller. In either case, there's no need to observe a Task from // this method, and async void simplifies the call (no need to receive and then ignore // the Task object just to keep the compiler quiet). private async void _CleanupClientAsync(ConnectedEndPoint client) { try { await client.ReadTask; } catch (IOException e) { _OnClientException(client, e.Message); } catch (Exception e) { // Unexpected exceptions are programmer-error. They could be anything, and leave // the program in an unknown, possibly corrupt state. The only reasonable disposition // is to log, then exit. // // Full stack-trace, because who knows what this exception was. Will need the // stack-trace to do any diagnostic work. _OnStatus($"Unexpected client connection exception. {e}"); Environment.Exit(1); } finally { _RemoveClient(client); client.Dispose(); } }
private static void _StartUserInput(ConnectedEndPoint server) { // Get user input in a new thread, so main thread can handle waiting // on connection. new Thread(() => { try { string line; while ((line = ReadLine()) != "") { server.WriteLine(line); } server.Shutdown(); } catch (IOException e) { WriteLine($"Server {server.RemoteEndPoint} IOException: {e.Message}"); } catch (Exception e) { WriteLine($"Unexpected server exception: {e}"); Environment.Exit(1); } }) { // Setting IsBackground means this thread won't keep the // process alive. So, if the connection is closed by the server, // the main thread can exit and the process as a whole will still // be able to exit. IsBackground = true }.Start(); }
private void _RemoveClient(ConnectedEndPoint client) { lock (_lock) { _clients.Remove(client); _OnStatus($"removed client {client.RemoteEndPoint} -- {_clients.Count} clients connected"); } }
private void _AddClient(ConnectedEndPoint client) { lock (_lock) { _clients.Add(client); _OnStatus($"added client {client.RemoteEndPoint} -- {_clients.Count} clients connected"); } }
static void Main(string[] args) { IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Loopback, _kportNumber); ConnectedEndPoint server = ConnectedEndPoint.Connect(remoteEndPoint, (c, s) => WriteLine(s)); _StartUserInput(server); _SafeWaitOnServerRead(server).Wait(); }
private static async Task _SafeWaitOnServerRead(ConnectedEndPoint server) { try { await server.ReadTask; } catch (IOException e) { WriteLine($"Server {server.RemoteEndPoint} IOException: {e.Message}"); } catch (Exception e) { // Should never happen. It's a bug in this code if it does. WriteLine($"Unexpected server exception: {e}"); } }
private async Task _ListenAsync() { try { while (true) { ConnectedEndPoint client = await ConnectedEndPoint.AcceptAsync(_listener, _ClientReadLine); _AddClient(client); _CleanupClientAsync(client); } } catch (ObjectDisposedException) { _OnStatus("Server's listening socket closed"); } catch (IOException e) { _OnStatus($"Listening socket IOException: {e.Message}"); } await _CleanupServerAsync(); }
private void _OnClientException(ConnectedEndPoint client, string message) { _OnStatus($"Client {client.RemoteEndPoint} IOException: {message}"); }