2010-05-23 88 views
3

我正在開發使用。NET的Socket異步模型異步遊戲服務器(BeginAccept/EndAccept ...等)。的.Net 3.5異步套接字服務器性能問題

我所面臨的問題是這樣描述的: 當我只有一個客戶端連接時,服務器響應時間非常快,但是一旦第二個客戶端連接,服務器響應時間就會增加太多。

我測量了從客戶端發送消息到服務器的時間,直到它在兩種情況下都得到回覆。我發現一個客戶的平均時間大約是17ms,而在2個客戶的情況下大約是280ms!我真正看到的是:當2個客戶端連接並且只有其中一個正在移動(即向服務器請求服務)時,它等同於只有一個客戶端連接(即快速響應)的情況。但是,當兩個客戶端同時移動時(即同時向服務器請求服務),他們的動作變得非常慢(就好像服務器按照順序而不是同時迴應它們中的每一個)。

基本上,我在做什麼是:

當客戶端請求許可運動從服務器和服務器授予他的請求,服務器然後廣播客戶端的新位置,所有的球員。因此,如果兩個客戶端在同一時間移動,服務器最終將同時向兩個客戶端廣播每個客戶端的新位置。

EX:

  • 客戶端1詢問去位置(2,2)
  • 客戶機2請求去位置(5,5)
  • 服務器發送到客戶端1的每個客戶機2 &相同的兩個消息:
  • MESSAGE1: 「客戶端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 
              }); 


     } 
+0

看起來你可能只能處理你在單線程上收到的數據包。 – sblom 2010-05-23 20:22:40

+0

我還沒有看過代碼,但有幾個問題?客戶是否忽視他們自己的移動信息 - 他們應該忽略他們?你多久發一次廣播消息? – 2010-05-23 20:31:19

+0

@ sblom-我不相信,事件是從回調函數「WaitForData」觸發的。我假設這個回調函數在數據包同時到達服務器時在另一個線程中執行 – iBrAaAa 2010-05-23 20:51:01

回答

1

你應該將

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

到行後

ClientInfo newClient = new ClientInfo 

這樣,您就不會阻止接受超過必要的新連接。

認爲,問題來自 一個事實,即Socket類是線程安全的

這不應該是有關您的方案。

您是否記得在套接字上設置NoDelay屬性來禁用Nagling?

+0

爲什麼建議NoDelay?它應該[幾乎永遠不會被使用](http://tangentsoft.net/wskfaq/intermediate.html#disable-nagle)。在我編寫的幾十個協議中,我只關閉了一個協議(這只是因爲它是一個串口協議,而不是TCP/IP協議)。 – 2010-05-24 02:32:56

+0

關於接受問題,你是對的,但這根本不是我的問題。我遇到的問題是同時移動2個客戶端,而不是同時連接。您可以放心地假定客戶端一個接一個連接,所以不會發生阻塞。 – iBrAaAa 2010-05-24 04:09:00

+1

我建議NoDelay是因爲他的目標不是移動大量數據,而是非常快速地移動小塊數據。 NoDelay是爲此設計的。您鏈接到的文章Stephen提到了這一點,所以即使您不打算,您也確實支持我的觀點。 – EricLaw 2010-05-24 15:01:07