2010-05-23 85 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