Проблема производительности асинхронного сервера сокетов .Net 3.5 - PullRequest
3 голосов
/ 24 мая 2010

Я разрабатываю асинхронный игровой сервер с использованием асинхронной модели .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
                                            });


        }

1 Ответ

1 голос
/ 24 мая 2010

Вы должны двигаться

//Start accepting clients asynchronously again 
mainSocket.BeginAccept(OnClientConnected, null); 

сразу после строки

ClientInfo newClient = new ClientInfo  

Чтобы вы не блокировали прием новых соединений дольше, чем необходимо.

считаю, что проблема исходит от тот факт, что класс Socket является потоком сейф

Это не должно относиться к вашему сценарию.

Вы не забыли установить свойство NoDelay в сокете для отключения Nagling?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...