I'm developing an Asynchronous Game Server using .Net Socket Asynchronous Model( BeginAccept/EndAccept...etc.)
The problem I'm facing is described like that:
When I have only one client connected the server response time is very fast but once a second client connects, the server response time increases too much.
I've measured the time from a client sends a message to the server until it gets the reply in both cases. I found that the average time in case of one client is about 17ms and in case of 2 clients about 280ms!!!
What I really see is that: When 2 clients are connected and only one of them is moving(i.e. requesting service from the server) it is equivalently equal to the case when only one client is connected. However, when the 2 clients move at the same time(i.e. requests service from the server at the same time) their motion becomes very slow (as if the server replies each one of them in order i.e. not simultaneously).
Basically, what I am doing is that:
When a client requests a permission for motion from the server and the server grants him the request, the server then broadcasts the new position of the client to all the players. So if two clients are moving in the same time, the server is eventually trying to broadcast to both clients the new position of each of them at the same time.
EX:
-Client1 asks to go to position (2,2)
-Client2 asks to go to position (5,5)
+Server sends to each of Client1 & Client2 two messages:
message1: "Client1 at (2,2)"
message2: "Client2 at (5,5)"
I believe that the problem comes from the fact that Socket class is thread safe according MSDN documentation
here.
Below is the code for the server:
public class NetworkManager
{
private const string PortNameConfigKey = "portNumber";
private readonly int port = 5000;
private readonly Hashtable connectedClients = new Hashtable();
private readonly ManualResetEvent resetEvent = new ManualResetEvent(false);
private int clientCount;
private readonly Socket mainSocket =
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private const int ClientDisconnectedErrorCode = 10054;
private static readonly NetworkManager networkManagerInstance = new NetworkManager();
public delegate void NewClientConnected(Object sender, SystemEventArgs e);
public delegate void PositionUpdateMessageRecieved(Object sender, PositionUpdateEventArgs e);
public delegate void AttackMessageRecieved(object sender, AttackEventArgs e);
public delegate void ClientDisconnected(object sender, ClientDisconnectedEventArgs e);
public delegate void DebugException(object sender, DebugEventArgs e);
public AttackMessageRecieved AttackMessageEvent
{
get;
set;
}
public PositionUpdateMessageRecieved PositionUpdateMessageEvent
{
get;
set;
}
public ClientDisconnected ClientDisconnectedEvent
{
get;
set;
}
public DebugException DebugExceptionEvent
{
get;
set;
}
public NewClientConnected NewClientEvent
{
get;
set;
}
public int ClientCount
{
get
{
return clientCount;
}
}
public static NetworkManager NetworkManagerInstance
{
get
{
return networkManagerInstance;
}
}
private NetworkManager()
{
string portValue = ConfigurationManager.AppSettings[PortNameConfigKey];
if (portValue != null)
{
try
{
port = Convert.ToInt32(portValue);
}
catch
{
throw new ConfigurationErrorsException(
string.Format(
"Error occurred during reading the configuration file."
+ " '{0}' configuration key is incorrect",
PortNameConfigKey));
}
}
}
public void StartServer()
{
mainSocket.Bind(new IPEndPoint(IPAddress.Any, port));
mainSocket.Listen(1024);
mainSocket.BeginAccept(OnClientConnected, null);
resetEvent.WaitOne();
}
private void OnClientConnected(IAsyncResult asyncResult)
{
Interlocked.Increment(ref clientCount);
ClientInfo newClient = new ClientInfo
{
WorkerSocket = mainSocket.EndAccept(asyncResult),
PlayerId = clientCount
};
connectedClients.Add(newClient.PlayerId, newClient);
if (NewClientEvent != null)
{
NewClientEvent(this, System.EventArgs.Empty);
}
newClient.WorkerSocket.BeginReceive(newClient.Buffer, 0, BasePacket.GetMaxPacketSize(),
SocketFlags.None, new AsyncCallback(WaitForData), newClient);
mainSocket.BeginAccept(OnClientConnected, null);
}
private void WaitForData(IAsyncResult asyncResult)
{
ClientInfo sendingClient = null;
try
{
sendingClient = asyncResult.AsyncState as ClientInfo;
if (!IsConnected(sendingClient.WorkerSocket))
{
throw new SocketException(ClientDisconnectedErrorCode);
}
sendingClient.WorkerSocket.EndReceive(asyncResult);
FireMessageTypeEvent(sendingClient.ConvertBytesToPacket() as BasePacket);
sendingClient.WorkerSocket.BeginReceive(sendingClient.Buffer, 0, BasePacket.GetMaxPacketSize(),
SocketFlags.None, new AsyncCallback(WaitForData), sendingClient);
}
catch (SocketException e)
{
if (DebugExceptionEvent != null)
{
DebugExceptionEvent(this, new DebugEventArgs(e));
}
if (e.ErrorCode == ClientDisconnectedErrorCode)
{
if (sendingClient.WorkerSocket != null)
{
sendingClient.WorkerSocket.Close();
sendingClient.WorkerSocket = null;
}
connectedClients.Remove(sendingClient.PlayerId);
if (ClientDisconnectedEvent != null)
{
ClientDisconnectedEvent(this, new ClientDisconnectedEventArgs(sendingClient.PlayerId));
}
}
}
catch (Exception e)
{
if (DebugExceptionEvent != null)
{
DebugExceptionEvent(this, new DebugEventArgs(e));
}
sendingClient.WorkerSocket.BeginReceive(sendingClient.Buffer, 0, BasePacket.GetMaxPacketSize(),
SocketFlags.None, new AsyncCallback(WaitForData), sendingClient);
}
}
public void BroadcastMessage(BasePacket message)
{
byte[] bytes = message.ConvertToBytes();
foreach (ClientInfo client in connectedClients.Values)
{
client.WorkerSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendAsync, client);
}
}
public void SendToClient(BasePacket message, int id)
{
byte[] bytes = message.ConvertToBytes();
(connectedClients[id] as ClientInfo).WorkerSocket.BeginSend(bytes, 0, bytes.Length,
SocketFlags.None, SendAsync, connectedClients[id]);
}
private void SendAsync(IAsyncResult asyncResult)
{
ClientInfo currentClient = (ClientInfo)asyncResult.AsyncState;
currentClient.WorkerSocket.EndSend(asyncResult);
}
void FireMessageTypeEvent(BasePacket packet)
{
switch (packet.MessageType)
{
case MessageType.PositionUpdateMessage:
if (PositionUpdateMessageEvent != null)
{
PositionUpdateMessageEvent(this, new PositionUpdateEventArgs(packet as PositionUpdatePacket));
}
break;
}
}
}
The events fired are handled in a different class, here are the event handling code for the PositionUpdateMessage (Other handlers are irrelevant):
private readonly Hashtable onlinePlayers = new Hashtable();
private GameController()
{
server = new Thread(networkManager.StartServer);
server.Start();
networkManager.PositionUpdateMessageEvent += OnPositionUpdateMessageReceived;
}
private void OnPositionUpdateMessageReceived(object sender, PositionUpdateEventArgs e)
{
Point currentLocation = ((PlayerData)onlinePlayers[e.PositionUpdatePacket.PlayerId]).Position;
Point locationRequested = e.PositionUpdatePacket.Position;
((PlayerData)onlinePlayers[e.PositionUpdatePacket.PlayerId]).Position = locationRequested;
networkManager.BroadcastMessage(new PositionUpdatePacket
{
Position = locationRequested,
PlayerId = e.PositionUpdatePacket.PlayerId
});
}
}