Okay, looking for some constructive feedback/criticism here...
What follows is the code of my c# UDP Socket Class. I've written this based on a number of online examples and a few distant memories from the past, so I really have no idea how "bad" the code is/could be in a 100+ concurrent connection scenario(my dev environment is just two machines at the moment)... It works, I know that, it is fairly stable(I can't break it when using it normally), and it behaves how I believe I want it to.
It is a dual purpose class for handling both Servers and Clients. It uses an asynchronous receive and synchronous send(I may switch to async send if it proves beneficial later on). My servers use Client sockets to communicate with each other and to perform internal connection tests in a multiple server, "single shard" type environment. I'll be devising some heavy load testing methods a little further down the line, but I'm hoping to fish out most of the major gotchas before I get there.
So, please, let me know how to fix it up if it smells terribly to you, or if not, that's even better...
Sorry for the large code dump, but THANKS for checking it out!
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UWAvatarServerData;
namespace UWAvatarServer
{
public class UDPSocket : IDisposable
{
//Some simple string length counters and averages to get a rough idea of generated traffic.
public int sndMax = 0;
public MovingAverage sndAvg = new MovingAverage();
public int csndMax = 0;
public MovingAverage csndAvg = new MovingAverage();
public int rcvMax = 0;
public MovingAverage rcvAvg = new MovingAverage();
//Constant for configuring the prevention of ICMP connection resets
private const int SIO_UDP_CONNRESET = -1744830452;
//UDP socket
private Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//Buffer Size Constant
private const int bufSize = 8 * 1024;
//Raw string data from client packets
private Dictionary<EndPoint, Queue<string>> messageDictionary;
//Queue for holding raw string data from server packets when in client mode.
private Queue<string> cQ;
//Referece to the data store used by the server(for access to the current game time clock)
private UWDataStore dataStore;
//Time code storage for last sent/received messages
private double lastSentMessage = 0;
private double lastReceivedMessage = 0;
//Boolean to determine which mode we're in so received messages get put in the right place.
private bool clientMode = false;
//Max string lenght allowed by the servers.
private int maxMessageLength = 1450;
//IDisposable stuff
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// free managed resources
if (_socket != null)
{
_socket.Dispose();
_socket = null;
}
}
}
//State class for async receive.
public class State
{
public byte[] buffer = new byte[bufSize];
public EndPoint epFrom = new IPEndPoint(IPAddress.Any, 0);
}
//Server "Mode"
public UDPSocket(Dictionary<EndPoint, Queue<string>> msgsDict, UWDataStore DATASTORE)
{
clientMode = false;
messageDictionary = msgsDict;
dataStore = DATASTORE;
lastSentMessage = dataStore.UWServerSeconds;
}
//Client "Mode"
public UDPSocket(Queue<string> mq, UWDataStore DATASTORE)
{
clientMode = true;
cQ = mq;
dataStore = DATASTORE;
lastSentMessage = dataStore.UWServerSeconds;
}
public void CloseSocket()
{
_socket.Close();
}
//Internal connection status checking
public int SendHowStale()
{
if (lastSentMessage == 0)
lastSentMessage = dataStore.UWServerSeconds;
return (int)(dataStore.UWServerSeconds - lastSentMessage);
}
//Internal connection status checking
public int ReceiveHowStale()
{
if (lastReceivedMessage == 0)
lastReceivedMessage = dataStore.UWServerSeconds;
return (int)(dataStore.UWServerSeconds - lastReceivedMessage);
}
//Start/Bind a Server.
public void Server(string address, int port)
{
//In case restarting uncleanly, dunno if this actually does anything..
_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
//Ensure all async packets contain endpoint info and etc.
_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true);
//Ignore ICMP port unreachable exceptions.
_socket.IOControl((IOControlCode)SIO_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null);
//Bind to port.
if (address == "all")
{
_socket.Bind(new IPEndPoint(IPAddress.Any, port));
}
else
{
_socket.Bind(new IPEndPoint(IPAddress.Parse(address), port));
}
//Start receive callback process.
Receive();
}
//Setup a Client to Server socket.
public void Client(string address, int port)
{
//Dunno if these two options do anything for client sockets, but they don't seem to break anything.
_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true);
_socket.IOControl((IOControlCode)SIO_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null);
_socket.Connect(IPAddress.Parse(address), port);
//Start receive callback.
Receive();
}
//ServerSend sends to any EndPoint from THIS server.
public void ServerSend(string text, EndPoint ep)
{
try
{
byte[] data = Encoding.ASCII.GetBytes(text);
_socket.SendTo(data, ep);
lastSentMessage = dataStore.UWServerSeconds;
if (text.Length > sndMax) sndMax = text.Length;
sndAvg.ComputeAverage((float)text.Length);
//Console.WriteLine("TO NET: " + text);
}
catch (Exception ex)
{
Console.WriteLine("ServerSend Exception: " + ex.Message);
}
}
//Client Send only sends to the connected Server.
public void cSend(string text)
{
try
{
byte[] data = Encoding.ASCII.GetBytes(text);
_socket.Send(data);
lastSentMessage = dataStore.UWServerSeconds;
if (text.Length > sndMax) sndMax = text.Length;
sndAvg.ComputeAverage((float)text.Length);
//Console.WriteLine("TO NET: " + text);
}
catch (Exception ex)
{
Console.WriteLine("cSend Exception: " + ex.Message);
}
}
//Setup Async Callback
private void Receive()
{
try
{
State so = new State();
_socket.BeginReceiveFrom(so.buffer, 0, bufSize, SocketFlags.None, ref so.epFrom, new AsyncCallback(_Receive), so);
}
catch (Exception)
{
}
}
//Receive Callback
private void _Receive(IAsyncResult ar)
{
try
{
State so = (State)ar.AsyncState;
int bytes = _socket.EndReceiveFrom(ar, ref so.epFrom);
lastReceivedMessage = dataStore.UWServerSeconds;
string smessage = Encoding.ASCII.GetString(so.buffer, 0, bytes);
//Console.WriteLine("FROM NET: " + text);
if (smessage.Length < maxMessageLength)
{
if (smessage.Length > rcvMax) rcvMax = smessage.Length;
rcvAvg.ComputeAverage((float)smessage.Length);
if (clientMode)
{
cQ.Enqueue(smessage);
}
else
{
if (!messageDictionary.ContainsKey(so.epFrom))
{
messageDictionary.Add(so.epFrom, new Queue<string> { });
}
messageDictionary[so.epFrom].Enqueue(smessage);
}
}
_socket.BeginReceiveFrom(so.buffer, 0, bufSize, SocketFlags.None, ref so.epFrom, new AsyncCallback(_Receive), so);
}
catch (Exception)
{
}
}
}
}
Generally speaking I use it as such:
public static bool running = true;
static void UDPServer()
{
using (s = new UDPSocket(MessageDictionary, UWDataStore))
{
s.Server("all", 37373);
while(running)
{
//Servery Stuff Goes Here.
//Like reiteratively dequeuing the Message Dictionary Queues and processing/replying to all commands/etc...
}
}
}
All processing of individual messages from clients is handled with Task.Factory tasks, and a reference to the server's socket varible (s), and the client's EndPoint is sent along the ride for use in replying to clients. There's no game logic and there are no client replies that happen directly from within the UDP server's main while loop, mostly just connection status checking and reorganizing of the Message Queues into Tasks.
Thanks again for making it this far.