public async Task BroadcastToAll(Message message) { await Task.Yield(); var tasks = from client in connectionManager.Clients select client.SendMessage(message); await Task.WhenAll(tasks); }
public async Task SendMessage(Message message) { using (var releaser = await sendingLock.LockAsync()) { await SendMessageWithoutInterleaving(message); } }
private async Task SendMessageWithoutInterleaving(Message message) { using (var sendStream = new MemoryStream()) { sendStream.SetLength(4); sendStream.Position = 4; ProtoBuf.Serializer.Serialize(sendStream, message); int messageLength = checked((int)sendStream.Position); int payloadLength = messageLength - 4; byte[] buffer = sendStream.GetBuffer(); WriteInt32BigEndian(payloadLength, sendStream.GetBuffer(), 0); int bytesWritten = 0; while (bytesWritten < messageLength) { int written = await socket.SendTaskAsync( buffer, bytesWritten, messageLength - bytesWritten, SocketFlags.None); bytesWritten += written; } } }
public IncomingMessage(ClientConnection sender, Message message) { Sender = sender; Message = message; }
public async Task ReplyTo(ClientConnection client, Message message) { await Task.Yield(); await client.SendMessage(message); }
private static void StartServer(Options options) { logger.Info("=========================="); logger.Info("Starting server..."); var cancellationSource = new CancellationTokenSource(); var cancellation = cancellationSource.Token; Console.CancelKeyPress += (sender, e) => { e.Cancel = true; logger.Info("Gracefully stopping server..."); cancellationSource.Cancel(); }; var threadPool = new Core.ThreadPool(10, cancellation); IClientAcceptor acceptor = new TcpClientAcceptor(options.Port); var connectionManager = new ConnectionManager(acceptor, cancellation); var broadcastingChat = new BroadcastingChat(connectionManager, options.WelcomeMessage); var commandShell = new CommandShell("cmd", command => $"/c {command}"); Func<ClientConnection, string, Task> processCommand = async (client, command) => { Message reply; if (command.StartsWith("/c ")) { var task = commandShell.TryStartExecuting(command.Substring(3), TimeSpan.FromSeconds(10)); if (task == null) reply = new Message { Sender = "<server-shell>", Text = "Another command is already running." }; else { await broadcastingChat.ReplyTo(client, new Message { Sender = "<server-shell>", Text = $"Running `{command}`..." }); try { reply = new Message { Sender = "<server-shell>", Text = $"Execution result: {Environment.NewLine}{await task}" }; } catch (OperationCanceledException) { reply = new Message { Sender = "<server-shell>", Text = $"Execution timed out." }; } } } else reply = new Message { Sender = "<server>", Text = $"Invalid command '{command}'" }; await broadcastingChat.ReplyTo(client, reply); }; broadcastingChat.IncomingMessageStrategy = incoming => { var message = incoming.Message.Text.TrimStart(); if (message.StartsWith("/")) return processCommand(incoming.Sender, message); else return broadcastingChat.BroadcastToAll(incoming.Message); }; var tcs = new TaskCompletionSource<bool>(); threadPool.Post(async () => { logger.Info($"Start accepting clients at port {options.Port}"); try { await acceptor.Listen(cancellation); logger.Info("Finish accepting clients"); tcs.TrySetResult(true); } catch (OperationCanceledException) { tcs.SetCanceled(); } catch (Exception ex) { logger.Error(ex, "Error accepting clients"); tcs.SetException(ex); } }); try { tcs.Task.Wait(); } catch (AggregateException ex) { try { ex.Handle(exc => exc is OperationCanceledException); } catch (Exception innerEx) { logger.Error(innerEx); } } logger.Info("Server stopped"); }