using System; using IotWeb.Common.Http; using System.Threading.Tasks; using System.Collections.Generic; using System.Diagnostics; using Nibriboard.Client.Messages; using System.Threading; using Nibriboard.RippleSpace; namespace Nibriboard.Client { /// /// Manages a group of s. /// public class NibriClientManager : IWebSocketRequestHandler { /// /// The ripple space manager that this client manager is connected to. /// public RippleSpaceManager SpaceManager { get; private set; } private ClientSettings clientSettings; public List Clients = new List(); public LineIncubator LineIncubator = new LineIncubator(); /// /// The cancellation token that's used by the main server to tell us when we should shut down. /// protected CancellationToken canceller; /// /// The interval at which heatbeats should be sent to the client. /// public readonly int HeatbeatInterval = 5000; /// /// The number of clients currently connected to this Nibriboard. /// public int ClientCount { get { return Clients.Count; } } public NibriClientManager(ClientSettings inClientSettings, RippleSpaceManager inSpaceManager, CancellationToken inCancellationToken) { clientSettings = inClientSettings; canceller = inCancellationToken; SpaceManager = inSpaceManager; } /// /// Whether we will accept a given new WebSocket connection or not. /// /// The uri the user connected to. /// The protocol the user is connecting with. /// Whether we want to accept the WebSocket connection attempt or not. public bool WillAcceptRequest(string uri, string protocol) { //Log.WriteLine("[Nibriboard/Websocket] Accepting new {0} connection.", protocol); return clientSettings.WebsocketProtocol == protocol; } /// /// Handles WebSocket clients when they first connect, wrapping them in /// a instance and adding them to /// the client list. /// /// New socket. public void Connected(WebSocket newSocket) { NibriClient client = new NibriClient(this, newSocket); client.Disconnected += handleDisconnection; // Clean up when the client disconnects Clients.Add(client); } /// /// Sends a message to all the connected clients, except the one who's sending it. /// /// The client sending the message. /// The message that is to bee sent. public void Broadcast(NibriClient sendingClient, Message message) { foreach(NibriClient client in Clients) { // Don't send the message to the sender if (client == sendingClient) continue; client.Send(message); } } /// /// Sends a message to everyone on the same plane as the sender, except the sender themselves. /// /// The sending client. /// The message to send. public void BroadcastPlane(NibriClient sendingClient, Message message) { foreach(NibriClient client in Clients) { // Don't send the message to the sender if(client == sendingClient) continue; // Only send the message to others on the same plane if(client.CurrentPlane != sendingClient.CurrentPlane) continue; client.Send(message); } } /// /// Sends a message to everyone on a specified plane. /// /// The plane to send the message to. /// The message to send. public void ReflectPlane(Plane plane, Message message) { foreach(NibriClient client in Clients) { if(client.CurrentPlane != plane) continue; client.Send(message); } } /// /// Periodically tidies up the client list, disconnecting old clients. /// private async Task ClientMaintenanceMonkey() { while (true) { // Exit if we've been asked to shut down if (canceller.IsCancellationRequested) { close(); return; } // Disconnect unresponsive clients. foreach (NibriClient client in Clients) { // If we haven't heard from this client in a little while, send a heartbeat message if(client.MillisecondsSinceLastMessage > HeatbeatInterval) client.SendHeartbeat(); // If the client hasn't sent us a message in a while (even though we sent // them a heartbeat to check on them on the last loop), disconnect them if (client.MillisecondsSinceLastMessage > HeatbeatInterval * 2) client.CloseConnection(new IdleDisconnectMessage()); } await Task.Delay(HeatbeatInterval); } } /// /// Cleans up this NibriClient manager ready for shutdown. /// private void close() { // Close the connection to all the remaining nibri clients, telling them that the server is about to shut down foreach (NibriClient client in Clients) client.CloseConnection(new ShutdownMessage()); } /// /// Clean up after a client disconnects from the server. /// /// The client that has disconnected. private void handleDisconnection(NibriClient disconnectedClient) { Clients.Remove(disconnectedClient); } } }