2013-06-30 47 views
-6

我正在爲我的小型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。 –

回答

3

我想提一提,因爲我相信這是回答你的問題的根源的第一件事,就是你的表現(延遲,併發連接能力等)將主要由您運行此軟件的硬件以及每個特定客戶端的網絡性能來定義。軟件可以改進一些東西,但一般來說,如果你的代碼編寫得很好,並且可以被其他人理解並且不包含錯誤,它就可以執行得很好。

您的代碼會同時處理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框架的異步套接字概念的問題。這是一項很多工作,對大多數人來說,需要的不止是一個週末的學習和練習。從小處着手,繼續玩下去,你會得到它。 –