-6

私は小さな 2D 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;
    }
}
4

1 に答える 1

3

私が最初に言及したいのは、それがあなたの質問の根本的な答えだと信じているので、あなたのパフォーマンス(レイテンシ、同時接続容量など)は、このソフトウェアを実行しているハードウェアと特定のクライアントごとのネットワーク パフォーマンス。ソフトウェアはいくつかのことを改善できますが、一般的に言えば、コードが適切に作成され、他の人が理解でき、バグが含まれていなければ、問題なく動作します。

コードは 300 の接続を同時に処理しますか? はい、ほとんどの場合可能です。ただし、スレッドに関する潜在的な問題がいくつか見られます。1つは、新しいクライアントを受け入れるときに多くの競合が発生することです. また、各クライアントが完全に受け入れられるまで待機することで脆弱性を作成しました。サービス拒否攻撃の可能性があります。クライアントは、パケットごとにデータの再送信をパケットごとに最大 3 回要求することで接続を停止でき、タイムアウト (10 秒?) になるまで各メッセージの配信を待機できます。また、実装したメソッド スタブ自体がスレッド セーフでない限り、データ処理にも多くの問題が発生します。

古い非同期ソケット モデルを使用しています。それが何であるかは少し複雑です。私の意見では、イベント駆動モデルの方がより自然であるため、イベント駆動モデルの方が少しよく理解できると思います。私の経験から、どちらもうまく機能することを知っています。IAsyncResultただし、オブジェクトの過剰な割り当てによる大規模なガベージ コレクションが行われないため、新しいイベント ドリブン モデルの方が少し高速であることもわかりました。新しいモデルはSocket.AcceptAsyncSocket.Completedイベントなどのメソッドを使用します。

あなたは C# を初めて使用するので、代わりに、非同期要素を持つシンプルでクリーンなクライアント/サーバー アプリケーションの作成に集中することをお勧めします。その上で負荷テストを実行し、ハードウェアの生のスループットに関してパフォーマンスの基準を満たしているかどうかを確認できます。これにより、分析の要因が減少します。簡単なテキスト メッセージをやり取りできるものから始めることをお勧めします。.NET のスレッド化とネットワーク通信のニュアンスをより深く理解するにつれて、複雑さを増すことができます。

http://www.albahari.com/threading/を調べることをお勧めする Web サイトの 1 つです。これには、マルチスレッド コードを作成するためのさまざまな構成要素が詳しく説明されています。おっしゃる通り、プログラミング経験のある方を対象としています。「高度なスレッド化」セクションは、私がよく参照するものです。

Andrew Troelson 著の本「Pro C# 2010 and the .NET 4 Platform」(ISBN 1430225491) も良いスタートになるでしょう。多くの言語をカバーし、C# と他の言語とのいくつかの類似点を示しています。また、多くの人が楽しいことに飛び込む前に十分に理解する必要がある .NET についても説明しています。

于 2013-06-30T04:03:56.120 に答える