Я разрабатываю асинхронный игровой сервер с использованием асинхронной модели .Net Socket (BeginAccept / EndAccept ... и т. Д.)
Проблема, с которой я сталкиваюсь, описывается так: когда у меня только один клиентподключено, время отклика сервера очень быстрое, но при подключении второго клиента время отклика сервера слишком сильно увеличивается.
Я измерил время, когда клиент отправляет сообщение на сервер, пока не получит ответв обоих случаях.Я обнаружил, что среднее время в случае одного клиента составляет около 17 мс, а в случае 2 клиентов - около 280 мс !!!
Что я действительно вижу, так это: когда 2 клиента подключены и только один из них движется(т.е. запрашивая услугу с сервера) это эквивалентно случаю, когда подключен только один клиент (т.е. быстрый ответ).Однако, когда 2 клиента перемещаются одновременно (т.е. запрашивают обслуживание у сервера одновременно), их движение становится очень медленным (как если бы сервер отвечал каждому из них по порядку, то есть не одновременно).
По сути, я делаю следующее:
Когда клиент запрашивает разрешение на движение от сервера и сервер предоставляет ему запрос, сервер затем передает новую позицию клиента всем игрокам.Таким образом, если два клиента перемещаются одновременно, сервер в конечном итоге пытается передать обоим клиентам новую позицию каждого из них одновременно.
EX:
- Client1 просит перейти в положение (2,2)
- Client2 просит перейти в положение (5,5)
- Сервер отправляет каждому из Client1 & Client2 одинаковые два сообщения:
- сообщение1: «Клиент1 в (2,2)»
- сообщение2: «Клиент2 в (5,5)»
Я считаю, что проблема связана с тем, что класс Socket является потокобезопасным в соответствии с документацией MSDN http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.aspx. (НЕ УВЕРЕН, ЧТО ЭТО ПРОБЛЕМА)
Ниже приведен код для сервера:
///
/// This class is responsible for handling packet receiving and sending
///
public class NetworkManager
{
///
/// An integer to hold the server port number to be used for the connections. Its default value is 5000.
///
private readonly int port = 5000;
///
/// hashtable contain all the clients connected to the server.
/// key: player Id
/// value: socket
///
private readonly Hashtable connectedClients = new Hashtable();
///
/// An event to hold the thread to wait for a new client
///
private readonly ManualResetEvent resetEvent = new ManualResetEvent(false);
///
/// keeps track of the number of the connected clients
///
private int clientCount;
///
/// The socket of the server at which the clients connect
///
private readonly Socket mainSocket =
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
///
/// The socket exception that informs that a client is disconnected
///
private const int ClientDisconnectedErrorCode = 10054;
///
/// The only instance of this class.
///
private static readonly NetworkManager networkManagerInstance = new NetworkManager();
///
/// A delegate for the new client connected event.
///
/// the sender object
/// the event args
public delegate void NewClientConnected(Object sender, SystemEventArgs e);
///
/// A delegate for the position update message reception.
///
/// the sender object
/// the event args
public delegate void PositionUpdateMessageRecieved(Object sender, PositionUpdateEventArgs e);
///
/// The event which fires when a client sends a position message
///
public PositionUpdateMessageRecieved PositionUpdateMessageEvent
{
get;
set;
}
///
/// keeps track of the number of the connected clients
///
public int ClientCount
{
get
{
return clientCount;
}
}
///
/// A getter for this class instance.
///
/// only instance.
public static NetworkManager NetworkManagerInstance
{
get
{
return networkManagerInstance;
}
}
private NetworkManager()
{}
/// Starts the game server and holds this thread alive
///
public void StartServer()
{
//Bind the mainSocket to the server IP address and port
mainSocket.Bind(new IPEndPoint(IPAddress.Any, port));
//The server starts to listen on the binded socket with max connection queue //1024
mainSocket.Listen(1024);
//Start accepting clients asynchronously
mainSocket.BeginAccept(OnClientConnected, null);
//Wait until there is a client wants to connect
resetEvent.WaitOne();
}
///
/// Receives connections of new clients and fire the NewClientConnected event
///
private void OnClientConnected(IAsyncResult asyncResult)
{
Interlocked.Increment(ref clientCount);
ClientInfo newClient = new ClientInfo
{
WorkerSocket = mainSocket.EndAccept(asyncResult),
PlayerId = clientCount
};
//Add the new client to the hashtable and increment the number of clients
connectedClients.Add(newClient.PlayerId, newClient);
//fire the new client event informing that a new client is connected to the server
if (NewClientEvent != null)
{
NewClientEvent(this, System.EventArgs.Empty);
}
newClient.WorkerSocket.BeginReceive(newClient.Buffer, 0, BasePacket.GetMaxPacketSize(),
SocketFlags.None, new AsyncCallback(WaitForData), newClient);
//Start accepting clients asynchronously again
mainSocket.BeginAccept(OnClientConnected, null);
}
/// Waits for the upcoming messages from different clients and fires the proper event according to the packet type.
///
///
private void WaitForData(IAsyncResult asyncResult)
{
ClientInfo sendingClient = null;
try
{
//Take the client information from the asynchronous result resulting from the BeginReceive
sendingClient = asyncResult.AsyncState as ClientInfo;
// If client is disconnected, then throw a socket exception
// with the correct error code.
if (!IsConnected(sendingClient.WorkerSocket))
{
throw new SocketException(ClientDisconnectedErrorCode);
}
//End the pending receive request
sendingClient.WorkerSocket.EndReceive(asyncResult);
//Fire the appropriate event
FireMessageTypeEvent(sendingClient.ConvertBytesToPacket() as BasePacket);
// Begin receiving data from this client
sendingClient.WorkerSocket.BeginReceive(sendingClient.Buffer, 0, BasePacket.GetMaxPacketSize(),
SocketFlags.None, new AsyncCallback(WaitForData), sendingClient);
}
catch (SocketException e)
{
if (e.ErrorCode == ClientDisconnectedErrorCode)
{
// Close the socket.
if (sendingClient.WorkerSocket != null)
{
sendingClient.WorkerSocket.Close();
sendingClient.WorkerSocket = null;
}
// Remove it from the hash table.
connectedClients.Remove(sendingClient.PlayerId);
if (ClientDisconnectedEvent != null)
{
ClientDisconnectedEvent(this, new ClientDisconnectedEventArgs(sendingClient.PlayerId));
}
}
}
catch (Exception e)
{
// Begin receiving data from this client
sendingClient.WorkerSocket.BeginReceive(sendingClient.Buffer, 0, BasePacket.GetMaxPacketSize(),
SocketFlags.None, new AsyncCallback(WaitForData), sendingClient);
}
}
///
/// Broadcasts the input message to all the connected clients
///
///
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);
}
}
///
/// Sends the input message to the client specified by his ID.
///
///
/// The message to be sent.
/// The id of the client to receive the message.
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);
}
/// Fires the event depending on the type of received packet
///
/// The received packet.
void FireMessageTypeEvent(BasePacket packet)
{
switch (packet.MessageType)
{
case MessageType.PositionUpdateMessage:
if (PositionUpdateMessageEvent != null)
{
PositionUpdateMessageEvent(this, new PositionUpdateEventArgs(packet as PositionUpdatePacket));
}
break;
}
}
}
Сработавшие события обрабатываются в другом классе, вот код обработки события для PositionUpdateMessage (другие обработчики не имеют значения):
private readonly Hashtable onlinePlayers = new Hashtable();
///
/// Constructor that creates a new instance of the GameController class.
///
private GameController()
{
//Start the server
server = new Thread(networkManager.StartServer);
server.Start();
//Create an event handler for the NewClientEvent of networkManager
networkManager.PositionUpdateMessageEvent += OnPositionUpdateMessageReceived;
}
///
/// this event handler is called when a client asks for movement.
///
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;
// Broadcast the new position
networkManager.BroadcastMessage(new PositionUpdatePacket
{
Position = locationRequested,
PlayerId = e.PositionUpdatePacket.PlayerId
});
}