.Net 3.5 Asynchronous Socket Server Performance Problem

I am developing an asynchronous game server using the asynchronous .Net Socket model (BeginAccept / EndAccept ... etc.)

The problem I am facing is described as follows: When I have only one client connected, the server response time is very fast, but as soon as the second client is connected, the server response time increases too much.

I measured the time from the client by sending a message to the server until it receives a response in both cases. I found that the average time in the case of one client is about 17 ms, and in the case of 2 clients it is about 280 ms.

I really see that: when 2 clients are connected and only one of them is moving (i.e., requesting a service from the server), it is equivalent to the case when only one client is connected (i.e., a quick response). However, when two clients move at the same time (i.e., Requests a service from the server at the same time), their movement becomes very slow (as if the server answered each of them, that is, not simultaneously).

Basically, I do what:

When a client requests permission to move from the server, and the server provides it with a request, the server then passes the new client position to all players. Therefore, if two clients move at the same time, the server ultimately tries to simultaneously transmit to each client a new position for each of them.

Example:

  • Client1 requests to move to position (2.2)
  • Client2 asks to go to position (5.5).
  • Client1 Client2 :
  • message1: "Client1 at (2,2)"
  • message2: "Client2 at (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
                                            });


        }
+3
1

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

ClientInfo newClient = new ClientInfo  

, .

, , Socket

.

NoDelay , Nagling?

+1

Source: https://habr.com/ru/post/1746785/


All Articles