2017-01-10 20:53:50 +00:00
|
|
|
|
using System;
|
|
|
|
|
using IotWeb.Common.Http;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Diagnostics;
|
2017-02-04 21:26:48 +00:00
|
|
|
|
using Nibriboard.Client.Messages;
|
2017-02-19 16:35:12 +00:00
|
|
|
|
using System.Threading;
|
2017-03-04 19:59:22 +00:00
|
|
|
|
using Nibriboard.RippleSpace;
|
2017-01-21 18:13:42 +00:00
|
|
|
|
|
|
|
|
|
namespace Nibriboard.Client
|
2017-01-10 20:53:50 +00:00
|
|
|
|
{
|
2017-01-28 18:49:26 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Manages a group of <see cref="Nibriboard.Client.NibriClient"/>s.
|
|
|
|
|
/// </summary>
|
2017-01-10 20:53:50 +00:00
|
|
|
|
public class NibriClientManager : IWebSocketRequestHandler
|
|
|
|
|
{
|
2017-03-04 19:59:22 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The ripple space manager that this client manager is connected to.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public RippleSpaceManager SpaceManager;
|
|
|
|
|
|
2017-01-22 20:56:31 +00:00
|
|
|
|
private ClientSettings clientSettings;
|
2017-01-28 18:41:35 +00:00
|
|
|
|
public List<NibriClient> Clients = new List<NibriClient>();
|
2017-01-21 18:38:52 +00:00
|
|
|
|
|
2017-02-19 16:35:12 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The cancellation token that's used by the main server to tell us when we should shut down.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected CancellationToken canceller;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The interval at which heatbeats should be sent to the client.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly int HeatbeatInterval = 5000;
|
|
|
|
|
|
2017-01-22 20:56:31 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The number of clients currently connected to this Nibriboard.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public int ClientCount {
|
|
|
|
|
get {
|
2017-01-28 18:41:35 +00:00
|
|
|
|
return Clients.Count;
|
2017-01-22 20:56:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-10 20:53:50 +00:00
|
|
|
|
|
2017-03-04 19:59:22 +00:00
|
|
|
|
public NibriClientManager(ClientSettings inClientSettings, RippleSpaceManager inSpaceManager, CancellationToken inCancellationToken)
|
2017-01-10 20:53:50 +00:00
|
|
|
|
{
|
2017-01-21 18:38:52 +00:00
|
|
|
|
clientSettings = inClientSettings;
|
2017-02-19 16:35:12 +00:00
|
|
|
|
canceller = inCancellationToken;
|
2017-03-04 19:59:22 +00:00
|
|
|
|
|
|
|
|
|
spaceManager = inSpaceManager;
|
2017-01-10 20:53:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether we will accept a given new WebSocket connection or not.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="uri">The uri the user connected to.</param>
|
|
|
|
|
/// <param name="protocol">The protocol the user is connecting with.</param>
|
|
|
|
|
/// <returns>Whether we want to accept the WebSocket connection attempt or not.</returns>
|
|
|
|
|
public bool WillAcceptRequest(string uri, string protocol)
|
|
|
|
|
{
|
2017-02-19 15:55:22 +00:00
|
|
|
|
//Log.WriteLine("[Nibriboard/Websocket] Accepting new {0} connection.", protocol);
|
2017-01-21 18:38:52 +00:00
|
|
|
|
return clientSettings.WebsocketProtocol == protocol;
|
2017-01-10 20:53:50 +00:00
|
|
|
|
}
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handles WebSocket clients when they first connect, wrapping them in
|
|
|
|
|
/// a <see cref="Nibriboard.NibriClient" /> instance and adding them to
|
|
|
|
|
/// the client list.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="newSocket">New socket.</param>
|
|
|
|
|
public void Connected(WebSocket newSocket)
|
|
|
|
|
{
|
|
|
|
|
NibriClient client = new NibriClient(this, newSocket);
|
2017-02-05 14:46:40 +00:00
|
|
|
|
client.Disconnected += handleDisconnection; // Clean up when the client disconnects
|
|
|
|
|
|
2017-01-28 18:41:35 +00:00
|
|
|
|
Clients.Add(client);
|
2017-01-10 20:53:50 +00:00
|
|
|
|
}
|
2017-01-22 20:56:31 +00:00
|
|
|
|
|
2017-02-05 14:46:40 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sends a message to all the connected clients, except the one who's sending it.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="sendingClient">The client sending the message.</param>
|
|
|
|
|
/// <param name="message">The message that is to bee sent.</param>
|
2017-02-04 21:26:48 +00:00
|
|
|
|
public void Broadcast(NibriClient sendingClient, Message message)
|
2017-01-22 20:56:31 +00:00
|
|
|
|
{
|
2017-01-28 18:41:35 +00:00
|
|
|
|
foreach(NibriClient client in Clients)
|
2017-01-22 20:56:31 +00:00
|
|
|
|
{
|
|
|
|
|
// Don't send the message to the sender
|
|
|
|
|
if (client == sendingClient)
|
|
|
|
|
continue;
|
|
|
|
|
|
2017-02-04 21:26:48 +00:00
|
|
|
|
client.Send(message);
|
2017-01-22 20:56:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-05 14:46:40 +00:00
|
|
|
|
|
2017-02-19 16:35:12 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Cleans up this NibriClient manager ready for shutdown.
|
|
|
|
|
/// </summary>
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-05 14:46:40 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Clean up after a client disconnects from the server.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="disconnectedClient">The client that has disconnected.</param>
|
|
|
|
|
private void handleDisconnection(NibriClient disconnectedClient)
|
|
|
|
|
{
|
|
|
|
|
Clients.Remove(disconnectedClient);
|
|
|
|
|
}
|
2017-01-10 20:53:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|