I am trying to implement a TCP/UDP server so all I have to do is something like this:
var server = new Server(Type.UDP, "127.0.0.1", 8888);server.OnDataRecieved += Datahandler;server.Start();
I have tried to make it perform as fast as possible by using Asynchronous calls where possible.
I basically would like to know if there is anything missing/any changes that people would recommend (and why). It is not finished yet as I need to handle exceptions better etc...
TODO: I need to complete the signature of the events to make them more meaningful, etc.
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Net;using System.Net.Sockets;using System.Threading;namespace SBlackler.Networking{public sealed class HighPerformanceServer{ private Int32 _currentConnections = 0; Socket listener; EndPoint ipeSender; #region "Properties" public Int32 Port { get; set; } public Int32 CurrentConnections { get { return _currentConnections; } } public Int32 MaxQueuedConnections { get; set; } public IPEndPoint Endpoint { get; set; } public ServerType Type { get; set; } #endregion #region "Constructors" private HighPerformanceServer() { // do nothing } public HighPerformanceServer(ServerType type, String IpAddress) { Init(type, IpAddress, 28930); } public HighPerformanceServer(ServerType type, String IpAddress, Int32 Port) { Init(type, IpAddress, Port); } private void Init(ServerType server, String IpAddress, Int32 Port) { IPAddress ip; // Check the IpAddress to make sure that it is valid if (!String.IsNullOrEmpty(IpAddress) && IPAddress.TryParse(IpAddress, out ip)) { this.Endpoint = new IPEndPoint(ip, Port); // Make sure that the port is greater than 100 as not to conflict with any other programs if (Port < 100) { throw new ArgumentException("The argument 'Port' is not valid. Please select a value greater than 100."); } else { this.Port = Port; } } else { throw new ArgumentException("The argument 'IpAddress' is not valid"); } // We never want a ServerType of None, but we include it as it is recommended by FXCop. if (server != ServerType.None) { this.Type = server; } else { throw new ArgumentException("The argument 'ServerType' is not valid"); } } #endregion #region "Events" public event EventHandler<EventArgs> OnServerStart; public event EventHandler<EventArgs> OnServerStarted; public event EventHandler<EventArgs> OnServerStopping; public event EventHandler<EventArgs> OnServerStoped; public event EventHandler<EventArgs> OnClientConnected; public event EventHandler<EventArgs> OnClientDisconnecting; public event EventHandler<EventArgs> OnClientDisconnected; public event EventHandler<EventArgs> OnDataReceived; #endregion public void Start() { // Tell anything that is listening that we have starting to work if (OnServerStart != null) { OnServerStart(this, null); } // Get either a TCP or UDP socket depending on what we specified when we created the class listener = GetCorrectSocket(); if (listener != null) { // Bind the socket to the endpoint listener.Bind(this.Endpoint); // TODO :: Add throttleling (using SEMAPHORE's) if (this.Type == ServerType.TCP) { // Start listening to the socket, accepting any backlog listener.Listen(this.MaxQueuedConnections); // Use the BeginAccept to accept new clients listener.BeginAccept(new AsyncCallback(ClientConnected), listener); } else if (this.Type == ServerType.UDP) { // So we can buffer and store information, create a new information class SocketConnectionInfo connection = new SocketConnectionInfo(); connection.Buffer = new byte[SocketConnectionInfo.BufferSize]; connection.Socket = listener; // Setup the IPEndpoint ipeSender = new IPEndPoint(IPAddress.Any, this.Port); // Start recieving from the client listener.BeginReceiveFrom(connection.Buffer, 0, connection.Buffer.Length, SocketFlags.None, ref ipeSender, new AsyncCallback(DataReceived), connection); } // Tell anything that is listening that we have started to work if (OnServerStarted != null) { OnServerStarted(this, null); } } else { // There was an error creating the correct socket throw new InvalidOperationException("Could not create the correct sever socket type."); } } internal Socket GetCorrectSocket() { if (this.Type == ServerType.TCP) { return new Socket(this.Endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); } else if (this.Type == ServerType.UDP) { return new Socket(this.Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); } else { return null; } } public void Stop() { if (OnServerStopping != null) { OnServerStopping(this, null); } if (OnServerStoped != null) { OnServerStoped(this, null); } } internal void ClientConnected(IAsyncResult asyncResult) { // Increment our ConcurrentConnections counter Interlocked.Increment(ref _currentConnections); // So we can buffer and store information, create a new information class SocketConnectionInfo connection = new SocketConnectionInfo(); connection.Buffer = new byte[SocketConnectionInfo.BufferSize]; // We want to end the async event as soon as possible Socket asyncListener = (Socket)asyncResult.AsyncState; Socket asyncClient = asyncListener.EndAccept(asyncResult); // Set the SocketConnectionInformations socket to the current client connection.Socket = asyncClient; // Tell anyone that's listening that we have a new client connected if (OnClientConnected != null) { OnClientConnected(this, null); } // TODO :: Add throttleling (using SEMAPHORE's) // Begin recieving the data from the client if (this.Type == ServerType.TCP) { asyncClient.BeginReceive(connection.Buffer, 0, connection.Buffer.Length, SocketFlags.None, new AsyncCallback(DataReceived), connection); } else if (this.Type == ServerType.UDP) { asyncClient.BeginReceiveFrom(connection.Buffer, 0, connection.Buffer.Length, SocketFlags.None, ref ipeSender, new AsyncCallback(DataReceived), connection); } // Now we have begun recieving data from this client, // we can now accept a new client listener.BeginAccept(new AsyncCallback(ClientConnected), listener); } internal void DataReceived(IAsyncResult asyncResult) { try { SocketConnectionInfo connection = (SocketConnectionInfo)asyncResult.AsyncState; Int32 bytesRead; // End the correct async process if (this.Type == ServerType.UDP) { bytesRead = connection.Socket.EndReceiveFrom(asyncResult, ref ipeSender); } else if (this.Type == ServerType.TCP) { bytesRead = connection.Socket.EndReceive(asyncResult); } else { bytesRead = 0; } // Increment the counter of BytesRead connection.BytesRead += bytesRead; // Check to see whether the socket is connected or not... if (IsSocketConnected(connection.Socket)) { // If we have read no more bytes, raise the data received event if (bytesRead == 0 || (bytesRead > 0 && bytesRead < SocketConnectionInfo.BufferSize)) { byte[] buffer = connection.Buffer; Int32 totalBytesRead = connection.BytesRead; // Setup the connection info again ready for another packet connection = new SocketConnectionInfo(); connection.Buffer = new byte[SocketConnectionInfo.BufferSize]; connection.Socket = ((SocketConnectionInfo)asyncResult.AsyncState).Socket; // Fire off the receive event as quickly as possible, then we can process the data... if (this.Type == ServerType.UDP) { connection.Socket.BeginReceiveFrom(connection.Buffer, 0, connection.Buffer.Length, SocketFlags.None, ref ipeSender, new AsyncCallback(DataReceived), connection); } else if (this.Type == ServerType.TCP) { connection.Socket.BeginReceive(connection.Buffer, 0, connection.Buffer.Length, SocketFlags.None, new AsyncCallback(DataReceived), connection); } // Remove any extra data if (totalBytesRead < buffer.Length) { Array.Resize<Byte>(ref buffer, totalBytesRead); } // Now raise the event, sender will contain the buffer for now if (OnDataReceived != null) { OnDataReceived(buffer, null); } buffer = null; } else { // Resize the array ready for the next chunk of data Array.Resize<Byte>(ref connection.Buffer, connection.Buffer.Length + SocketConnectionInfo.BufferSize); // Fire off the receive event again, with the bigger buffer if (this.Type == ServerType.UDP) { connection.Socket.BeginReceiveFrom(connection.Buffer, 0, connection.Buffer.Length, SocketFlags.None, ref ipeSender, new AsyncCallback(DataReceived), connection); } else if (this.Type == ServerType.TCP) { connection.Socket.BeginReceive(connection.Buffer, 0, connection.Buffer.Length, SocketFlags.None, new AsyncCallback(DataReceived), connection); } } } else if(connection.BytesRead > 0) { // We still have data Array.Resize<Byte>(ref connection.Buffer, connection.BytesRead); // call the event if (OnDataReceived != null) { OnDataReceived(connection.Buffer, null); } } } catch (Exception ex) { Console.WriteLine(ex.Message); } } internal bool IsSocketConnected(Socket socket) { return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0); } internal void DisconnectClient(SocketConnectionInfo connection) { if (OnClientDisconnecting != null) { OnClientDisconnecting(this, null); } connection.Socket.BeginDisconnect(true, new AsyncCallback(ClientDisconnected), connection); } internal void ClientDisconnected(IAsyncResult asyncResult) { SocketConnectionInfo sci = (SocketConnectionInfo)asyncResult; sci.Socket.EndDisconnect(asyncResult); if (OnClientDisconnected != null) { OnClientDisconnected(this, null); } }}public class SocketConnectionInfo{ public const Int32 BufferSize = 1048576; public Socket Socket; public byte[] Buffer; public Int32 BytesRead { get; set; }}public enum ServerType{ None = 0, TCP = 1, UDP = 2}}