C#异步服务器套接字 - 线程安全/性能(MMO游戏)

C#异步服务器套接字 - 线程安全/性能(MMO游戏)

问题描述:

我正在为我的小型2D MMO游戏编写此游戏服务器。C#异步服务器套接字 - 线程安全/性能(MMO游戏)

因此,这里是我的问题:

  1. 你想想代码的线程安全怎么办? 你能告诉我问题在哪里以及如何解决它们/修补它们?

这里是我的代码:

using System; 
using System.Net; 
using System.Net.Sockets; 
using System.Text; 
using System.Threading; 
using System.Collections.Generic; 
using System.Data; 
using System.Data.SqlClient; 
using System.Data.SqlTypes; 

public class Constanti 
{ 
    public const int CLASS_DARKELF_MAGICIAN = 1; 
    public const int CLASS_HUMAN_MAGICIAN = 2; 
    public const int CLASS_WARRIOR   = 3; 
    public const int CLASS_MODERN_GUNMAN = 4; 
    public const int SUIT_1 = 1; 
    public const int SUIT_2 = 2; 
    public const int SUIT_3 = 3; 
    public const int SUIT_4 = 4; 
    public const int SUIT_Admin = 5; 

    //MAX/MIN 
    public const int MAX_LEVEL = 100; 
    public const int MAX_SKILL_LEVEL = 1000; 

    //SERVER MAX/MIN 
    public const int MAX_CONNECTIONS = 300; 
    public const int MAX_CONNECTIONS_IP = 4; 
} 

// State object for reading client data asynchronously 
public class Player 
{ 
    // Client socket. 
    public Socket workSocket = null; 
    // Size of receive buffer. 
    public const int BufferSize = 1024; 
    // Receive buffer. 
    public byte[] buffer = new byte[BufferSize]; 
    // Received data string. 
    public StringBuilder sb = new StringBuilder(); 
    //Player-Info 
    public int PlayerStats_Health = 0; 
    public int PlayerStats_Energy = 0; 
    public int PlayerInfo_Class = 0; 
    public int PlayerInfo_Suit = 0; 
    public int PlayerInfo_Level = 0; 
    public int PlayerInfo_SkillLevel = 0; 

    public void SetDefaults() 
    { 
     PlayerStats_Health = 100; 
     PlayerStats_Energy = 200; 
     PlayerInfo_Class = Constanti.CLASS_DARKELF_MAGICIAN; 
     PlayerInfo_Suit = Constanti.SUIT_1; 
     PlayerInfo_Level = 1; 
     PlayerInfo_SkillLevel = 1; 
    } 

    public Player() 
    { 
    } 

    public String pIPAddress; 
} 

public class GameObjectLists 
{ 
    public static List<Player> PlayersList = new List<Player>(); 
} 

public class AsynchronousSocketListener 
{ 
    // Thread signal. 
    public static ManualResetEvent allDone = new ManualResetEvent(false); 
    public static int PlayersOnline = 0; 
    public AsynchronousSocketListener() 
    {} 

    public static void InitializeMySQL() 
    { 
     //TODO MySQLI/MySQL Connection 
    } 

    public static void MysqlUpdateQuery() 
    { 
     //Mysql UPDATE, no return stmt 
    } 

    public static String MySQLSelect() 
    { 
     //TODO MySQL Select 
     String retdata="test"; 

     return retdata; 
    } 

    public static void StartListening() 
    { 
     // Data buffer for incoming data. 
     byte[] bytes = new Byte[1024]; 
     // Establish the local endpoint for the socket. 
     // The DNS name of the computer 
     /* 
     IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName()); 
     IPAddress ipAddress = ipHostInfo.AddressList[0];*/ 

     IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 86); 

     // Create a TCP/IP socket. 
     Socket listener = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp); 

     // Bind the socket to the local endpoint and listen for incoming connections. 
     try 
     { 
      listener.Bind(localEndPoint); 
      listener.Listen(50); 
      Console.WriteLine("Server Started, waiting for connections..."); 

      while (true) 
      { 
       // Set the event to nonsignaled state. 
       allDone.Reset(); 

       // Start an asynchronous socket to listen for connections. 

       listener.BeginAccept(
        new AsyncCallback(AcceptCallback), 
        listener); 

       // Wait until a connection is made before continuing. 
       allDone.WaitOne(); 
      } 
     } 
     catch (Exception e) 
     { 
      Console.WriteLine(e.ToString()); 
     } 

     //Console.WriteLine("\nPress ENTER to continue..."); 
     Console.Read(); 
    } 


    public static void AcceptCallback(IAsyncResult ar) 
    { 
     // Get the socket that handles the client request. 
     Socket listener  = (Socket)ar.AsyncState; 
     Socket clientsocket = listener.EndAccept(ar); 
     // Signal the main thread to continue. 
     allDone.Set(); 
     clientsocket.Blocking = false;  //set to non-blocking 
     // Create the state object. 
     Player PlayerInfo = new Player(); 
     PlayerInfo.workSocket = clientsocket; 

     IPEndPoint thisIpEndPoint = PlayerInfo.workSocket.RemoteEndPoint as IPEndPoint; //Get Local Ip Address 
     PlayerInfo.pIPAddress = thisIpEndPoint.Address.ToString(); 

     GameObjectLists.PlayersList.Add(PlayerInfo); 
     PlayersOnline++; 

     int numconnsofip = 0; 
     GameObjectLists.PlayersList.ForEach(delegate(Player PlayerInfoCheck) 
     { 
       //Console.WriteLine(name); 
       if (PlayerInfoCheck.pIPAddress == PlayerInfo.pIPAddress) 
       { 
        numconnsofip++; 
       } 
     }); 

     if (PlayersOnline > Constanti.MAX_CONNECTIONS || numconnsofip > Constanti.MAX_CONNECTIONS_IP) 
     { 
      Disconnect(clientsocket, PlayerInfo); 
     } 
     else 
     { 
      Console.WriteLine("Player with IP:[{0}] has [{1}] Connections", thisIpEndPoint.Address.ToString(), numconnsofip); 
      PlayerInfo.SetDefaults(); 
      //clientsocket.LingerState = new LingerOption(true, 2); // give it up to 2 seconds for send 
      Console.WriteLine("New Connection Total:[{0}]", PlayersOnline); 
      clientsocket.BeginReceive(PlayerInfo.buffer, 0, Player.BufferSize, 0, new AsyncCallback(ReadCallback), 
       PlayerInfo); 
     } 
    } 

    public static void ProtocolCore(Player PlayerInfo, String data) 
    { 
     Console.WriteLine("Procesing Packet:{0}",data); 
     //if data == bla bla then send something to everyone: 

     GameObjectLists.PlayersList.ForEach(delegate(Player ObjPlayerInfo) 
     { 
      Send(data,ObjPlayerInfo); 
     }); 
    } 

    public static void ReadCallback(IAsyncResult ar) 
    { 
     // TEST #1 - IF WE HANG HERE, THERE WILL BE STILL OTHER CONNECTIONS COMING HERE, BUT NO MULTI THREADING?? 
     // Retrieve the state object and the clientsocket socket 
     // from the asynchronous state object. 
     Player PlayerInfo = (Player)ar.AsyncState; 
     Socket clientsocket = PlayerInfo.workSocket; 
     try 
     { 
      String content = String.Empty; //content buffer 

      // Read data from the client socket. 
      // IF THIS FAILS, WE CATCH/ASSUMING THAT: 
      // THE CLIENT FORCE-CLOSED THE CONNECTION OR OTHER REASON. 
      int bytesRead = clientsocket.EndReceive(ar);  

      if (bytesRead > 0) 
      { 
       // There might be more data, so store the data received so far. 

       PlayerInfo.sb.Append(Encoding.ASCII.GetString(
        PlayerInfo.buffer, 0, bytesRead)); 

       // Check for end-of-file tag. If it is not there, read 
       // more data. 
       content = PlayerInfo.sb.ToString(); 
       int eofindex = content.IndexOf("<EOF>"); 
       if (eofindex > -1) 
       { 
        // All the data has been read from the 
        // client. Display it on the console. 
        content = content.Substring(0,eofindex); //remove THE <EOF> 

        Console.WriteLine("Read {0} bytes from socket. Data : {1}",content.Length, content); 

        //PROCESS THE PACKET/DATA (PROTOCOL CORE) 
        ProtocolCore(PlayerInfo, content); 

        //Echo the data back to the client. 
        Send(content, PlayerInfo); 
        // CLEAR THE BUFFERS 
        PlayerInfo.sb.Remove(0, PlayerInfo.sb.Length); 
        Array.Clear(PlayerInfo.buffer, 0, PlayerInfo.buffer.Length); 

        // GO TO LISTEN FOR NEW DATA 
        clientsocket.BeginReceive(PlayerInfo.buffer, 0, Player.BufferSize, 0, 
        new AsyncCallback(ReadCallback), PlayerInfo); 
       } 
       else 
       { 
        // Not all data received. Get more. 
        clientsocket.BeginReceive(PlayerInfo.buffer, 0, Player.BufferSize, 0, 
        new AsyncCallback(ReadCallback), PlayerInfo); 
       } 
      } 
      else 
      { 
       //ASSUMING WE RECEIVED 0 SIZED PACKET or CLIENT DISCONNECT/THEREFORE CLOSE THE CONNECTION 
       Disconnect(clientsocket, PlayerInfo); 
      } 
     } 
     catch (Exception e) 
     { 
      Console.WriteLine(e.ToString()); 
      Disconnect(clientsocket, PlayerInfo); 
     } 
    } 

    private static void Send(String data,Player PlayerInfo) 
    { 
     // Convert the string data to byte data using ASCII encoding. 
     byte[] byteData = Encoding.ASCII.GetBytes(data); 

     // Begin sending the data to the remote device. 
     PlayerInfo.workSocket.BeginSend(byteData, 0, byteData.Length, 0, 
      new AsyncCallback(SendCallback), PlayerInfo); 
    } 

    private static void Disconnect(Socket clientsocket, Player PlayerInfo) 
    { 

     try 
     { 
      PlayersOnline--; //Is this Thread-Safe also? 
      GameObjectLists.PlayersList.Remove(PlayerInfo); 
      Console.WriteLine("Socket Disconnected, PlayerObjects:[{0}]", GameObjectLists.PlayersList.Count); 
      clientsocket.Shutdown(SocketShutdown.Both); 
      clientsocket.Close(); 
     } 
     catch (Exception e) 
     { 
      Console.WriteLine(e.ToString()); 
     } 
    } 

    private static void SendCallback(IAsyncResult ar) 
    { 
     // Retrieve the socket from the state object. 
     Player PlayerInfo = (Player)ar.AsyncState; 
     Socket clientsocket = PlayerInfo.workSocket; 
     try 
     { 
      // Complete sending the data to the remote device. 
      int bytesSent = clientsocket.EndSend(ar); 
      Console.WriteLine("Sent {0} bytes to client.", bytesSent); 
     } 
     catch (Exception e) 
     { 
      Console.WriteLine(e.ToString()); 
      Disconnect(clientsocket, PlayerInfo); 
     } 
    } 


    public static int Main(String[] args) 
    { 
     InitializeMySQL(); 
     StartListening(); 
     return 0; 
    } 
} 
+0

这个Stack Exchange网站专门介绍了游戏开发:http://gamedev.stackexchange.com/ –

+2

zespri看起来更适合[** codereview **](http://codereview.stackexchange。com /),@Tenev你的代码不是线程安全的并且有冲突。 – Prix

+0

我会同意@Prix这个更适合codereview。然而,这并不是特定于视频游戏开发,而视频游戏元素更多的是当你可以使用这些技术的情况下的实际例子。它不应该被重新定位到gamedev。 –

我想提一提,因为我相信这是回答你的问题的根源的第一件事,就是你的表现(延迟,并发连接能力等)将主要由您运行此软件的硬件以及每个特定客户端的网络性能来定义。软件可以改进一些东西,但一般来说,如果你的代码编写得很好,并且可以被其他人理解并且不包含错误,它就可以执行得很好。

您的代码会同时处理300个连接吗?是的,最有可能的。我确实看到了线程的一些潜在问题。一个是当你接受新客户时,你会有很多争议。您还通过在每个客户端的接受之间等待被完全接受来创建漏洞,这是一个拒绝服务攻击的可能性。客户端可以通过要求每个数据包重传数据(每个数据包最多三次)来停止连接,并且可以等待每当您的超时(10秒?)时发送每条消息。数据处理也会有很多问题,除非你实现它们的方法存根本身是线程安全的。

您正在使用较旧的异步套接字模型。它的内容有点复杂。我认为你会更好地理解事件驱动模型,因为在我看来它更自然。我知道,从我的经验来看,要么表现得很好。但是,我还发现新的事件驱动模型要快一点,因为由于过度分配IAsyncResult对象而没有执行大量垃圾回收。较新的模型使用诸如Socket.AcceptAsyncSocket.Completed事件的方法。

由于您是C#的新手,我建议您应该将精力集中在编写具有异步元素的简单且干净的客户端/服务器应用程序上。您可以对其进行负载测试,并根据硬件的原始吞吐量查看它是否满足您的性能标准。这将减少分析中的因素。我建议从可以来回传递简单文本消息的东西开始。在深入了解.NET中线程和网络通信的细微差别时,您可以增加复杂性。

我建议您考虑的一个网站是http://www.albahari.com/threading/,这对编写多线程代码的各种构建块有很好的解释。它针对的是编程经验丰富的人,就像你刚才提到的那样。 “高级线程”部分是我经常参考的内容。

Andrew Troelson(ISBN 1430225491)的书“Pro C#2010和.NET 4 Platform”也是一个好的开始。它涵盖了很多语言,并展示了C#和其他语言之间的一些相似之处。它还涵盖了大量的.NET,这是大多数人在深入了解有趣的东西之前真正需要了解的。

+0

该代码是从MSDN,http://msdn.microsoft.com/en-us/library/fx6588te.aspx – Tenev

+0

如果你可以让它线程安全和修复dos问题做它因为我从这个.net得到什么东西:D – Tenev

+0

@Tenev我也认为MSDN的代码示例在几年前将是一个很好的起点。看起来他们写得很差。这不仅是线程安全并解决DoS问题的问题。这是完全理解.NET框架的异步套接字概念的问题。这是一项很多工作,对大多数人来说,需要的不止是一个周末的学习和练习。从小处着手,继续玩下去,你会得到它。 –