For my current project I need a basic UDP server. The first priority is to make it as fast, but as resource friendly as possible (it will be run on Raspberry Pi-like devices).
I would like a review about the general code 'quality' and logic. I know that I am missing some error-handling at the moment.
The Server
class:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Nuernberger.FlyingDMX
{
public class Server
{
// UDPClient.Receive needs a reference as parameter
private IPEndPoint _endPoint;
public IPEndPoint Endpoint
{
get { return _endPoint; }
private set { _endPoint = value; }
}
// The server loop exists after setting this to false (+25ms max timeout time)
public bool Listening { get; private set; }
private UdpClient listener;
private Thread serverThread;
private ManualResetEvent threadBlocker = new ManualResetEvent(false);
// How long should the server try to receive a packet?
private const int SERVER_READ_TIMEOUT = 25;
// How often should the server look for new packets? (In ms)
private const int SERVER_LOOP_LIMIT = 5;
public event EventHandler<IncomingCommandEventArgs> OnCommandIncoming;
public event EventHandler<ServerStartStopEventArgs> OnServerStart;
public event EventHandler<ServerStartStopEventArgs> OnServerStop;
/// <summary>
/// Initalizies a new instance of the FlyingDMX.Server-Class and bind's it to the given port
/// </summary>
/// <param name="port"> The port the server should be bound to</param>
public Server(short port = 3636)
{
this.Endpoint = new IPEndPoint(IPHelper.GetBroadcastIP(), port);
this.listener = new UdpClient(port);
this.listener.EnableBroadcast = true;
this.listener.Client.ReceiveTimeout = SERVER_READ_TIMEOUT;
}
/// <summary>
/// Starts listening on the given port for UDP packet's
/// </summary>
/// <param name="blockThread"> Blocks the method until the listening loop exists</param>
public void Start(bool blockThread = false)
{
this.serverThread = new Thread(() =>
{
this.Listening = true;
if (this.OnServerStart != null)
this.OnServerStart(this, new ServerStartStopEventArgs(this.Endpoint));
while (this.Listening)
{
// listener.Available counts packet's we have to process. Wait if none are ready.
if(this.listener.Available > 0)
{
try
{
Byte[] data = this.listener.Receive(ref this._endPoint);
var receivedString = Encoding.ASCII.GetString(data);
if (this.OnCommandIncoming != null)
this.OnCommandIncoming(this, new IncomingCommandEventArgs(Command.TryParse(receivedString)));
}
catch(SocketException ex)
{
if (ex.ErrorCode != 10060)
{
// Handle the error. 10060 is a timeout error, which is expected.
}
}
Thread.Sleep(SERVER_LOOP_LIMIT);
}
}
});
this.serverThread.Start();
if (blockThread)
threadBlocker.WaitOne();
}
/// <summary>
/// Stops the server and unblocks the thread if needed.
/// </summary>
public void Stop()
{
this.Listening = false;
this.listener.Close();
// Let the server loop exit gracefully
Thread.Sleep(SERVER_READ_TIMEOUT + SERVER_LOOP_LIMIT + 10);
this.serverThread = null;
// Release the thread block on the Start method
this.threadBlocker.Set();
if (this.OnServerStop != null)
this.OnServerStop(this, new ServerStartStopEventArgs(this.Endpoint));
}
}
}
In case it's relevant, the TryParse
method of the Command
class:
public static Command TryParse(string text)
{
return new Command((CommandType)Enum.Parse(typeof(CommandType), text.Split(':')[0]), text.Split(':')[1].Split(';'));
}
The form of the incoming commands is:
CommandType:Arg1;Arg2;Arg3;...