mirror of
https://github.com/sbrl/Nibriboard.git
synced 2018-01-10 21:33:49 +00:00
Put some infrastructure in palce to better handle disconnects and shutdowns.
This commit is contained in:
parent
66b16acd3d
commit
686dd2f56d
9 changed files with 163 additions and 3 deletions
16
Nibriboard/Client/Messages/HeartbeatMessage.cs
Normal file
16
Nibriboard/Client/Messages/HeartbeatMessage.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
|
||||
namespace Nibriboard.Client.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a heartbeat message. The server sends this periodically to the client,
|
||||
/// to which the client should respond with an identical message.
|
||||
/// </summary>
|
||||
public class HeartbeatMessage : Message
|
||||
{
|
||||
public HeartbeatMessage()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
Nibriboard/Client/Messages/IdleDisconnectMessage.cs
Normal file
15
Nibriboard/Client/Messages/IdleDisconnectMessage.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace Nibriboard.Client.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Sent by the server to an idle client who's not responding to heartbeats just before the client disconnects them.
|
||||
/// </summary>
|
||||
public class IdleDisconnectMessage : Message
|
||||
{
|
||||
public IdleDisconnectMessage()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,13 @@ namespace Nibriboard.Client.Messages
|
|||
{
|
||||
public class Message
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the event that this message is about.
|
||||
/// </summary>
|
||||
public readonly string Event = string.Empty;
|
||||
/// <summary>
|
||||
/// The date and time that this message was sent.
|
||||
/// </summary>
|
||||
public readonly DateTime TimeSent = DateTime.Now;
|
||||
|
||||
public Message()
|
||||
|
|
15
Nibriboard/Client/Messages/ShutdownMessage.cs
Normal file
15
Nibriboard/Client/Messages/ShutdownMessage.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace Nibriboard.Client.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// A message that's sent by the server to a client to tell them that the server is shutting down.
|
||||
/// </summary>
|
||||
public class ShutdownMessage : Message
|
||||
{
|
||||
public ShutdownMessage()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -52,10 +52,26 @@ namespace Nibriboard.Client
|
|||
public bool Connected = true;
|
||||
public event NibriDisconnectedEvent Disconnected;
|
||||
|
||||
/// <summary>
|
||||
/// The date and time at which the last message was received from this client.
|
||||
/// </summary>
|
||||
public DateTime LastMessageTime = DateTime.Now;
|
||||
/// <summary>
|
||||
/// The number of milliseconds since we last received a message from this client.
|
||||
/// </summary>
|
||||
/// <value>The milliseconds since last message.</value>
|
||||
public int MillisecondsSinceLastMessage {
|
||||
get {
|
||||
return (int)((DateTime.Now - LastMessageTime).TotalMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this client has completed the handshake yet or not.
|
||||
/// </summary>
|
||||
public bool HandshakeCompleted = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The name this client has assignedto themselves.
|
||||
/// </summary>
|
||||
|
@ -102,18 +118,22 @@ namespace Nibriboard.Client
|
|||
client.ConnectionClosed += (WebSocket socket) => {
|
||||
Connected = false;
|
||||
Disconnected(this);
|
||||
Log.WriteLine("[NibriClient] Client #{0} disconnected.", Id);
|
||||
};
|
||||
}
|
||||
|
||||
private async Task handleMessage(string frame)
|
||||
{
|
||||
// Updatet he last time we received a message from the client
|
||||
LastMessageTime = DateTime.Now;
|
||||
|
||||
// Extract the event name from the message that the client sent.
|
||||
string eventName = JsonUtilities.DeserializeProperty<string>(frame, "Event");
|
||||
|
||||
if(eventName == null) {
|
||||
Log.WriteLine("[NibriClient#{0}] Received message that didn't have an event.", Id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!messageEventTypes.ContainsKey(eventName)) {
|
||||
Log.WriteLine("[NibriClient#{0}] Received message with invalid event {1}.", Id, eventName);
|
||||
return;
|
||||
|
@ -155,6 +175,28 @@ namespace Nibriboard.Client
|
|||
client.Send(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a heartbeat message to this client.
|
||||
/// </summary>
|
||||
public void SendHeartbeat()
|
||||
{
|
||||
Send(new HeartbeatMessage());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection to the client gracefully.
|
||||
/// </summary>
|
||||
public void CloseConnection(Message lastMessage)
|
||||
{
|
||||
if (!Connected)
|
||||
return;
|
||||
|
||||
// Tell the client that we're shutting down
|
||||
Send(lastMessage);
|
||||
|
||||
client.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new ClientState object representing this client's state at the current time.
|
||||
/// </summary>
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Nibriboard.Client.Messages;
|
||||
using System.Threading;
|
||||
|
||||
namespace Nibriboard.Client
|
||||
{
|
||||
|
@ -15,6 +16,16 @@ namespace Nibriboard.Client
|
|||
private ClientSettings clientSettings;
|
||||
public List<NibriClient> Clients = new List<NibriClient>();
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <summary>
|
||||
/// The number of clients currently connected to this Nibriboard.
|
||||
/// </summary>
|
||||
|
@ -24,9 +35,10 @@ namespace Nibriboard.Client
|
|||
}
|
||||
}
|
||||
|
||||
public NibriClientManager(ClientSettings inClientSettings)
|
||||
public NibriClientManager(ClientSettings inClientSettings, CancellationToken inCancellationToken)
|
||||
{
|
||||
clientSettings = inClientSettings;
|
||||
canceller = inCancellationToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -71,6 +83,41 @@ namespace Nibriboard.Client
|
|||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean up after a client disconnects from the server.
|
||||
/// </summary>
|
||||
|
|
|
@ -20,6 +20,9 @@ class RippleLink extends EventEmitter
|
|||
this.websocket.addEventListener("message", this.handleMessage.bind(this));
|
||||
this.websocket.addEventListener("close", this.handleDisconnection.bind(this));
|
||||
|
||||
// Respond to heartbeats from the server
|
||||
this.on("Heartbeat", this.handleHeartbeat.bind(this));
|
||||
|
||||
// Close the socket correctly
|
||||
window.addEventListener("beforeunload", (function(event) {
|
||||
this.websocket.close();
|
||||
|
@ -46,6 +49,16 @@ class RippleLink extends EventEmitter
|
|||
this.emit(message.Event, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replies to heartbeat messages from the server.
|
||||
*/
|
||||
handleHeartbeat(message) {
|
||||
// Reply with a heartbeat
|
||||
this.send({
|
||||
"Event": "Heartbeat"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message object to the server.
|
||||
*/
|
||||
|
|
|
@ -82,6 +82,9 @@
|
|||
<Compile Include="Utilities\ColourHSL.cs" />
|
||||
<Compile Include="Utilities\ToStringJsonConverter.cs" />
|
||||
<Compile Include="Client\Messages\HandshakeResponseMessage.cs" />
|
||||
<Compile Include="Client\Messages\ShutdownMessage.cs" />
|
||||
<Compile Include="Client\Messages\IdleDisconnectMessage.cs" />
|
||||
<Compile Include="Client\Messages\HeartbeatMessage.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="ClientFiles\index.html" />
|
||||
|
|
|
@ -6,6 +6,7 @@ using IotWeb.Common.Http;
|
|||
|
||||
using Nibriboard.RippleSpace;
|
||||
using Nibriboard.Client;
|
||||
using System.Threading;
|
||||
|
||||
namespace Nibriboard
|
||||
{
|
||||
|
@ -20,6 +21,8 @@ namespace Nibriboard
|
|||
private ClientSettings clientSettings;
|
||||
private RippleSpaceManager planeManager = new RippleSpaceManager();
|
||||
|
||||
private readonly CancellationTokenSource clientManagerCanceller = new CancellationTokenSource();
|
||||
|
||||
public readonly int Port = 31586;
|
||||
|
||||
public NibriboardServer(int inPort = 31586)
|
||||
|
@ -51,7 +54,7 @@ namespace Nibriboard
|
|||
// Websocket setup
|
||||
httpServer.AddWebSocketRequestHandler(
|
||||
clientSettings.WebsocketPath,
|
||||
new NibriClientManager(clientSettings)
|
||||
new NibriClientManager(clientSettings, clientManagerCanceller.Token)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue