-6

I'm writing this game server for my small 2D MMO game.

So here are my questions:

  1. What do you think about the Thread-Safety of the code? can you show me where the problems are and how to fix them / patch them?

Here is my code:

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;
    }
}
Tenev
  • 251
  • 6
  • 17
  • Game development is specifically covered by this Stack Exchange website: http://gamedev.stackexchange.com/ – Andrew Savinykh Jun 30 '13 at 03:49
  • 2
    zespri looks more suitable for [**codereview**](http://codereview.stackexchange.com/), @Tenev your code is not thread safe and have collisions. – Prix Jun 30 '13 at 03:52
  • I would agree with @Prix that this is more suited for codereview. This however is not specific to video game development and the video game element is more of a practical example of a situation when you can use these techniques. It should not be relocated to gamedev. – Michael J. Gray Jun 30 '13 at 04:05
  • @Tenev you don't have to be rude and yes there are plenty of people to understand it and that use c# there. – Prix Jun 30 '13 at 04:55

1 Answers1

3

The first thing I want to mention, since I believe it is answers the root of your question, is that your performance (latency, concurrent connection capacity, etc) is going to be largely defined by the hardware you are running this software on and the network performance for each specific client. Software can improve some things but generally speaking, if your code is well written and understandable by others and contains no bugs, it will perform just fine.

Will your code handle 300 connections concurrently? Yes most likely it can. I do see some potential issues with threading though. One being that you will have a lot of contention when you accept new clients. You also created a vulnerability by waiting between accepts for each client to be fully accepted, a potential for a denial of service attack. A client can stall the connection by requiring re-transmissions of data for each packet up to three times per packet and can wait to deliver each message up to whenever your timeout is (10 seconds?). There will also be a lot of problems with data processing unless the method stubs you have are thread safe themselves once you implement them.

You are using the older asynchronous socket model. It's a little complicated for what it is. I think that you will understand the event driven model a little better since it is more natural in my opinion. I know that from my experience, either perform just fine. However, I have also found that the new event driven model is a bit faster since you do not have huge garbage collections being done due to over allocation of IAsyncResult objects. The newer model uses methods such as Socket.AcceptAsync and the Socket.Completed event.

Since you are new to C#, I would recommend that instead you should be focusing efforts on writing a simple and clean client/server application that has asynchronous elements. You can do a load test on that and see if it satisfies your criteria for performance in terms of the raw throughput on your hardware. This will reduce the factors in your analysis. I recommend starting with something that can communicate simple text messages back and forth. You can increase the complexity as you gain a deeper understanding of the nuances of threading and network communication in .NET.

One website I recommend you looking into is http://www.albahari.com/threading/, this has a good explanation of the various building blocks for writing multi-threaded code. It's aimed at people with programming experience, as you mentioned you have. The "advanced threading" section is what I reference often.

The book "Pro C# 2010 and the .NET 4 Platform" by Andrew Troelson (ISBN 1430225491) would be a good start as well. It covers a lot of the language and shows some parallels between C# and other languages. It also covers a lot of .NET which is what most people really need to get a good understanding of before diving into the fun stuff.

Michael J. Gray
  • 9,784
  • 6
  • 38
  • 67
  • The code is from MSDN, http://msdn.microsoft.com/en-us/library/fx6588te.aspx – Tenev Jun 30 '13 at 04:19
  • If you can make it thread safe and fix the dos prob do it coz i get nothing from this .net stuff :D – Tenev Jun 30 '13 at 04:20
  • @Tenev I too thought code examples from MSDN would be a good starting place, some years ago. They're still poorly written it seems. It is not only a matter of making it thread safe and fixing the DoS problem. It is a matter of fully understanding the .NET framework's asynchronous socket concepts. It is a lot of work and requires more than a weekend of study and practice for most people. Start small and just keep playing with it, you'll get it in the end. – Michael J. Gray Jun 30 '13 at 04:22
  • And one more thing, i've tested the server code with 5 flooders and it has no threading problems so far whatsoever – Tenev Jun 30 '13 at 04:22
  • @Tenev If you have used those "flooders" and there are no issues, then you're good to go and you have answered your own question. If you could describe your process and answer your own question here, I will gladly upvote it. – Michael J. Gray Jun 30 '13 at 04:24
  • 1. Compile and run the code... 2.Well download net-tools, run 5 of them, run the http flooder (dos) on port 86; type 100 connections on each of them, Press start. so.. i dont see multithreading problems, nor errors... so my next question will be why is this thread-safe probably. – Tenev Jun 30 '13 at 04:28
  • i found the new asynchronous socket model but i find it very unusefull. – Tenev Jun 30 '13 at 09:18