MTSC  1.0.5
Build TCP servers out of modules with handlers for communication, exception and logging.
Server.cs
1 using MTSC.Exceptions;
2 using MTSC.Logging;
3 using MTSC.Server.Handlers;
4 using System;
5 using System.Collections.Concurrent;
6 using System.Collections.Generic;
7 using System.Net;
8 using System.Net.Security;
9 using System.Net.Sockets;
10 using System.Security.Cryptography.X509Certificates;
11 using System.Text;
12 using System.Threading;
13 using System.Threading.Tasks;
14 
15 namespace MTSC.Server
16 {
20  public sealed class Server
21  {
22  #region Fields
23  bool running;
24  X509Certificate2 certificate;
25  TcpListener listener;
26  int port = 50;
27  List<ClientData> toRemove = new List<ClientData>();
28  List<ClientData> clients = new List<ClientData>();
29  List<IHandler> handlers = new List<IHandler>();
30  List<ILogger> loggers = new List<ILogger>();
31  List<IExceptionHandler> exceptionHandlers = new List<IExceptionHandler>();
32  ConcurrentQueue<Tuple<ClientData, byte[]>> messageQueue = new ConcurrentQueue<Tuple<ClientData, byte[]>>();
33  int loopMillis = 16;
34  #endregion
35  #region Properties
36  public int Port { get => port; set => port = value; }
43  public bool Running { get => running; }
47  public List<ClientData> Clients { get => clients; set => clients = value; }
52  public bool ScaleUsage { get; set; }
58  public int TickRate { get => 1000 / loopMillis; set => loopMillis = 1000 / Math.Min(value, 1000); }
62  public bool ForceTickrate { get; set; }
63  #endregion
64  #region Constructors
65  public Server()
69  {
70 
71  }
76  public Server(int port)
77  {
78  this.port = port;
79  }
85  public Server(X509Certificate2 certificate, int port)
86  {
87  this.certificate = certificate;
88  this.port = port;
89  }
90  #endregion
91  #region Public Methods
92  public Server SetPort(int port)
98  {
99  this.port = port;
100  return this;
101  }
107  public Server AddHandler(IHandler handler)
108  {
109  handlers.Add(handler);
110  return this;
111  }
117  public Server AddLogger(ILogger logger)
118  {
119  loggers.Add(logger);
120  return this;
121  }
128  {
129  exceptionHandlers.Add(handler);
130  return this;
131  }
137  public void QueueMessage(ClientData target, byte[] message)
138  {
139  messageQueue.Enqueue(new Tuple<ClientData, byte[]>(target, message));
140  }
145  public void Log(string log)
146  {
147  foreach (ILogger logger in loggers)
148  {
149  logger.Log(log);
150  }
151  }
156  public void LogDebug(string debugMessage)
157  {
158  foreach (ILogger logger in loggers)
159  {
160  logger.LogDebug(debugMessage);
161  }
162  }
166  public void Run()
167  {
168  listener?.Stop();
169  listener = new TcpListener(IPAddress.Any, port);
170  listener.Start();
171  running = true;
172  Log("Server started on: " + listener.LocalEndpoint.ToString());
173  DateTime lastLoad = DateTime.Now;
174  DateTime startLoopTime;
175  while (running)
176  {
177  startLoopTime = DateTime.Now;
178  /*
179  * Check the client states. If a client is disconnected,
180  * remove it from the list of clients.
181  */
182  try
183  {
184  foreach (ClientData client in clients)
185  {
186  if (!client.TcpClient.Connected || client.ToBeRemoved)
187  {
188  toRemove.Add(client);
189  }
190  }
191  foreach (ClientData client in toRemove)
192  {
193  foreach (IHandler handler in handlers)
194  {
195  handler.ClientRemoved(this, client);
196  }
197  LogDebug("Client removed: " + client.TcpClient.Client.RemoteEndPoint.ToString());
198  client.SslStream?.Dispose();
199  client.TcpClient?.Dispose();
200  clients.Remove(client);
201  }
202  toRemove.Clear();
203  }
204  catch (Exception e)
205  {
206  LogDebug("Exception: " + e.Message);
207  LogDebug("Stacktrace: " + e.StackTrace);
208  foreach (IExceptionHandler exceptionHandler in exceptionHandlers)
209  {
210  if (exceptionHandler.HandleException(e))
211  {
212  break;
213  }
214  }
215  }
216  /*
217  * Check if the server has any pending connections.
218  * If it has a new connection, process it.
219  */
220  try
221  {
222  if (listener.Pending())
223  {
224  lastLoad = DateTime.Now;
225  TcpClient tcpClient = listener.AcceptTcpClient();
226  ClientData clientStruct = new ClientData(tcpClient);
227  if (certificate != null)
228  {
229  SslStream sslStream = new SslStream(tcpClient.GetStream(), true, new RemoteCertificateValidationCallback((o, c, ch, po) => {
230  return true;
231  }), null, EncryptionPolicy.RequireEncryption);
232  clientStruct.SslStream = sslStream;
233  sslStream.AuthenticateAsServer(certificate);
234  }
235  foreach (IHandler handler in handlers)
236  {
237  if (handler.HandleClient(this, clientStruct))
238  {
239  break;
240  }
241  }
242  clients.Add(clientStruct);
243  Log("Accepted new connection: " + tcpClient.Client.RemoteEndPoint.ToString());
244  }
245  }
246  catch(Exception e)
247  {
248  LogDebug("Exception: " + e.Message);
249  LogDebug("Stacktrace: " + e.StackTrace);
250  foreach (IExceptionHandler exceptionHandler in exceptionHandlers)
251  {
252  if (exceptionHandler.HandleException(e))
253  {
254  break;
255  }
256  }
257  }
258  /*
259  * Process in parallel all clients.
260  */
261  Parallel.ForEach(clients, (client) =>
262  {
263  try
264  {
265  /*
266  * If the connection has been lost, mark the client to be removed.
267  * Else, check if there is data to be read.
268  */
269  if (!client.TcpClient.Connected)
270  {
271  client.ToBeRemoved = true;
272  }
273  else if (client.TcpClient.Available > 0)
274  {
275  lastLoad = DateTime.Now;
276  Message message = CommunicationPrimitives.GetMessage(client.TcpClient, client.SslStream);
277  LogDebug("Received message from " + client.TcpClient.Client.RemoteEndPoint.ToString() +
278  "\nMessage length: " + message.MessageLength);
279  foreach (IHandler handler in handlers)
280  {
281  if (handler.PreHandleReceivedMessage(this, client, ref message))
282  {
283  break;
284  }
285  }
286  foreach (IHandler handler in handlers)
287  {
288  if (handler.HandleReceivedMessage(this, client, message))
289  {
290  break;
291  }
292  }
293  }
294  }
295  catch(Exception e)
296  {
297  LogDebug("Exception: " + e.Message);
298  LogDebug("Stacktrace: " + e.StackTrace);
299  foreach(IExceptionHandler exceptionHandler in exceptionHandlers)
300  {
301  if (exceptionHandler.HandleException(e))
302  {
303  break;
304  }
305  }
306  }
307  });
308  /*
309  * Iterate through all the handlers, running periodic operations.
310  */
311  Parallel.ForEach(handlers, (handler) =>
312  {
313  try
314  {
315  handler.Tick(this);
316  }
317  catch (Exception e)
318  {
319  LogDebug("Exception: " + e.Message);
320  LogDebug("Stacktrace: " + e.StackTrace);
321  foreach (IExceptionHandler exceptionHandler in exceptionHandlers)
322  {
323  if (exceptionHandler.HandleException(e))
324  {
325  break;
326  }
327  }
328  }
329  });
330  /*
331  * Check if there are messages queued to be sent.
332  */
333  if(messageQueue.Count > 0)
334  {
335  lastLoad = DateTime.Now;
336  try
337  {
338  while (messageQueue.Count > 0)
339  {
340  Tuple<ClientData, byte[]> queuedOrder = null;
341  if (messageQueue.TryDequeue(out queuedOrder))
342  {
343  Message sendMessage = CommunicationPrimitives.BuildMessage(queuedOrder.Item2);
344  for (int i = handlers.Count - 1; i >= 0; i--)
345  {
346  IHandler handler = handlers[i];
347  ClientData client = queuedOrder.Item1;
348  handler.HandleSendMessage(this, client, ref sendMessage);
349  }
350  CommunicationPrimitives.SendMessage(queuedOrder.Item1.TcpClient, sendMessage, queuedOrder.Item1.SslStream);
351  }
352  }
353  }
354  catch (Exception e)
355  {
356  LogDebug("Exception: " + e.Message);
357  LogDebug("Stacktrace: " + e.StackTrace);
358  foreach (IExceptionHandler exceptionHandler in exceptionHandlers)
359  {
360  if (exceptionHandler.HandleException(e))
361  {
362  break;
363  }
364  }
365  }
366  }
367  /*
368  * If the server load is low or the server has a forced tickrate,
369  * sleep an amount of milliseconds to lower the CPU usage.
370  */
371  if(((DateTime.Now - lastLoad).TotalMilliseconds > 1000) && ScaleUsage ||
373  {
374  Thread.Sleep((int)Math.Max(loopMillis - (DateTime.Now - startLoopTime).TotalMilliseconds, 0));
375  }
376  }
377  }
381  public Task RunAsync()
382  {
383  return new Task(Run);
384  }
388  public void Stop()
389  {
390  if (running)
391  {
392  running = false;
393  }
394  }
395  #endregion
396  #region Private Methods
397  #endregion
398  }
402  public class ClientData
403  {
404  public TcpClient TcpClient;
405  public DateTime LastMessageTime;
406  public bool ToBeRemoved;
407  public SslStream SslStream;
408 
409  public ClientData(TcpClient client)
410  {
411  this.TcpClient = client;
412  this.LastMessageTime = DateTime.Now;
413  ToBeRemoved = false;
414  SslStream = null;
415  }
416  }
417 }
Interface for communication handlers.
Definition: IHandler.cs:11
Server(int port)
Creates an instance of server.
Definition: Server.cs:76
int TickRate
How many times does the server proc per second. Cannot be set higher than 1000. This tickrate will be...
Definition: Server.cs:58
int Port
Server port.
Definition: Server.cs:39
bool Log(string message)
Logs the received message.
Task RunAsync()
Runs the server async.
Definition: Server.cs:381
bool HandleException(Exception e)
Handles exceptions.
Structure containing client information.
Definition: Server.cs:402
bool Running
Returns the state of the server.
Definition: Server.cs:43
bool ForceTickrate
If set to true, the server will respect the provided TickRate, regardless of current server load.
Definition: Server.cs:62
Server SetPort(int port)
Sets the port to the specified value.
Definition: Server.cs:97
Server()
Creates an instance of server with default values.
Definition: Server.cs:68
void QueueMessage(ClientData target, byte[] message)
Queues a message to be sent.
Definition: Server.cs:137
void ClientRemoved(Server server, ClientData client)
Handles the removal of a client from the server.
bool ScaleUsage
If set to true, the server will use an algorithm to lower or increase the CPU usage based on demands.
Definition: Server.cs:52
void Run()
Blocking method. Runs the server on the current thread.
Definition: Server.cs:166
Basic server class to handle TCP connections.
Definition: Server.cs:20
Interface for loggers.
Definition: ILogger.cs:10
Server AddHandler(IHandler handler)
Adds a handler to the server.
Definition: Server.cs:107
List< ClientData > Clients
List of clients currently connected to the server.
Definition: Server.cs:47
Server AddLogger(ILogger logger)
Adds a logger to the server.
Definition: Server.cs:117
bool HandleClient(Server server, ClientData client)
Handles a new client.
void Stop()
Stop the server.
Definition: Server.cs:388
Server AddExceptionHandler(IExceptionHandler handler)
Adds an exception handler to the server.
Definition: Server.cs:127
void LogDebug(string debugMessage)
Adds a debug message to be logged by the associated loggers.
Definition: Server.cs:156
Server(X509Certificate2 certificate, int port)
Creates an instance of server.
Definition: Server.cs:85
bool HandleSendMessage(Server server, ClientData client, ref Message message)
Handles a message before sending.
Definition: Client.cs:14
bool LogDebug(string message)
Logs the received debug message.
void Log(string log)
Adds a message to be logged by the associated loggers.
Definition: Server.cs:145
Handler to be used for handling exception.