Setting up a Server for a Multiplayer Game

Started by
12 comments, last by Oconzer 9 years, 2 months ago

After asking about the possibility of protecting save data on a clients computer for a game that I'm developing that was going to be single player with multiplayer, I'm going to have to figure out how to set up a actual server for hosting a server for my game (which is a TCG).

I've not a clue what I'd do or how long it would take to get a server setup or what not to host a server program for my game.

I've got basic networking knowledge as I've had to self teach myself how to do some networking (only have done a simple TCP console chat program). I understand that with making a server program that its to handle everything for the game (aside from the drawing) and that the client only draws and sends what the user is trying to do to the server for it to interpret what action the user wants to take.

I had originally intended to make the game offline mostly (because I do dislike the whole you have to be online to play a single player game) but since I won't be able to prevent cheating enough that way I have to go the server hosted route.

Advertisement
That's interesting!
Do you also have a question?
enum Bool { True, False, FileNotFound };

Yeah I apparently forgot to ask sorry. >.<

What does it take to setup (or get someplace to host) a server (physical not the program) and, what all do I need to know to set up the server program or is what I know fine enough for now?

The networking code that I know at the moment from my server / client console chat program:

// Main code of console chat program
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ClientTest;
using ServerTest;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace MainProgram
{
    class ClientServerTest
    {
        #region Messages
        public static string PMain = "Enter a command: \n\t/C to connect to a server\n\t/H to host a server\n\t/E to quit";
        public static string PMInvalid = "Invalid Command. Valid commands are:\n\t/C to connect to a server\n\t/H to host a server\n\t/E to quit";
        public static string PQuit = "Enter some text to send, or /q to stop hosting and return to main menu.";
        public static string PConnect = "Enter an address to connect to or /C to return: ";
        #endregion Messages

        public static AsyncClient CLIENT = new AsyncClient();
        public static AsyncServer SERVER = new AsyncServer(3);

        const int port = 11000;

        public static void HostAndRun()
        {
            IPAddress sip = null;
            IPHostEntry ipEntry = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress a in ipEntry.AddressList)
            {
                if (a.AddressFamily == AddressFamily.InterNetwork)
                {
                    sip = a;
                    break;
                }
            }
            IPEndPoint ServerEndPoint = new IPEndPoint(sip, port);
            SERVER.Host(ServerEndPoint);
            CLIENT.Host(ServerEndPoint);

            string Feed = string.Empty;

            Console.WriteLine(PQuit);

            while (!CLIENT.isQuitting)
            {
                CLIENT.RunClient();
                if (!CLIENT.isQuitting)
                {
                    SERVER.RunServer();
                }
                else
                {
                    CLIENT.StopConnection();
                    SERVER.StopServer();
                }
            }
            Console.WriteLine(PMain);
        }

        public static void ConnectAndRun()
        {
            bool loop = true;
            IPAddress ip = null;

            string serverIP = string.Empty;

            Console.WriteLine(PConnect);

            while (loop)
            {
                serverIP = Console.ReadLine();
                switch (serverIP.ToUpper())
                {
                    case "/C":
                        loop = false;
                        serverIP = string.Empty;
                        break;
                    default:
                        if (serverIP != string.Empty)
                        {
                            try
                            {
                                ip = IPAddress.Parse(serverIP);
                                loop = false;
                            }
                            catch (FormatException fe)
                            {
                                Console.WriteLine("Invalid address\n");
                                Console.WriteLine(PConnect);
                                serverIP = string.Empty;
                                loop = true;
                            }
                        }
                        break;
                }
            }
            if (serverIP != string.Empty)
            {
                IPEndPoint remoteEP = new IPEndPoint(ip, port);
                CLIENT.Start(remoteEP);
            }
            CLIENT.StopConnection();
            Console.WriteLine(PMain);
        }

        public static void Run()
        {
            bool loop = true;

            string command = string.Empty;

            Console.WriteLine(PMain);
            while (loop)
            {
                command = Console.ReadLine();
                switch (command.ToUpper())
                {
                    case "/C":
                        ConnectAndRun();
                        break;
                    case "/H":
                        HostAndRun();
                        break;
                    case "/E":
                        loop = false;
                        break;
                    default:
                        Console.WriteLine(PMInvalid);
                        break;
                }
            }
        }

        public static int Main(string[] args)
        {
            Console.Title = "ClientServer Test Program";

            Run();

            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
            return 0;
        }
    }
}
 
// Misc code for console chat program
using System;
using System.Net;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.IO;
using System.Threading;

namespace MiscStuff
{
    public enum ReasonForQuit
    {
        Default = 0, ServerFull = 1
    }

    class BufferSpace
    {
        public const int BufferSize = 512;
    }

    public class QuitReason
    {
        public static string CloseReason(ReasonForQuit reason = ReasonForQuit.Default)
        {
            string output = string.Empty;

            switch (reason)
            {
                case ReasonForQuit.Default:
                    break;
                case ReasonForQuit.ServerFull:
                    output = "<#Reason#>:FULL";
                    break;
            }

            return output;
        }
    }

    // State object for reading client data asynchronously
    public class Client
    {
        // Client socket.
        public byte[] readBuffer = new byte[BufferSpace.BufferSize];
        public byte[] writeBuffer = new byte[BufferSpace.BufferSize];
        private Socket Socket = null;
        public Socket clientSocket
        {
            get { return Socket; }
            set { Socket = value; }
        }

        //create a network stream for easier access
        private NetworkStream outStream;
        public NetworkStream WriteFeed
        {
            get { return outStream; }
        }

        //use a stream reader because of ReadLine() method
        private NetworkStream inStream;
        public NetworkStream ReadFeed
        {
            get { return inStream; }
        }

        public bool toRemove = false;

        public string feedBack = string.Empty;
        public StringBuilder sb = new StringBuilder();
        public IPEndPoint ID;

        public Client(Socket s)
        {
            if (s != null)
            {
                Socket = s;
                ID = (IPEndPoint)s.RemoteEndPoint;
                outStream = new NetworkStream(s);
                inStream = new NetworkStream(s);
            }
        }

        public void Set(Socket s)
        {
            Socket = s;
            if (s != null)
            {
                ID = (IPEndPoint)s.RemoteEndPoint;
                outStream = new NetworkStream(s);
                inStream = new NetworkStream(s);
            }
        }
    }
}
 
// client code for console chat program
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using MiscStuff;

namespace ClientTest
{
    using qr = MiscStuff.QuitReason;

    public class AsyncClient
    {
        #region Messages
        public string PFConnect = "Failed to connect to server.\nType /R to try connecting again or type /C to return to main.";
        public string PFInvald = "Invalid Command. Type /R to try connecting again or type /C to return to main.";
        public static string PDC = "Enter some text to send, or /q to disconnect and return to main menu.";
        public static string PConnected = "Connected to the server.\n";
        #endregion Messages

        private int ConTryTimeOut = 6000;
        private Client ThisClient = new Client(null);

        private string Feed = string.Empty;

        // ManualResetEvent instances signal completion.
        private static ManualResetEvent connectDone;
        private static ManualResetEvent sendDone;
        private static ManualResetEvent receiveDone;
        private static ManualResetEvent disconnetDone;
        private static ManualResetEvent issueCheckDone;

        private bool Started;
        private bool AttempingCon;

        private bool Quit;
        public bool isQuitting
        {
            get { return Quit; }
        }

        private bool Hosting = false;
        private IPEndPoint ID;

        public AsyncClient()
        {
        }

        private void SetupClientSocket()
        { // Resolving local machine information
            ThisClient.clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        public void Reset()
        {
            connectDone = new ManualResetEvent(false);
            sendDone = new ManualResetEvent(false);
            receiveDone = new ManualResetEvent(false);
            disconnetDone = new ManualResetEvent(false);
            issueCheckDone = new ManualResetEvent(false);
            AttempingCon = false;
            Started = false;
            Quit = false;
        }

        public void Start(IPEndPoint id)
        {
            Reset();
            ID = id;
            Hosting = false;
            StartConnection();
            CheckForIssues();
            while (AttempingCon)
            {
                if (AttempingCon)
                {
                    Console.WriteLine(PFConnect);
                }
                AttemptConnect();
            }
            if (Started)
            {
                Console.WriteLine(PConnected);
                Console.WriteLine(PDC);
                while (!Quit)
                {
                    RunClient();
                }
            }
        }

        public void RunClient()
        {
            if (Connected(ThisClient.clientSocket))
            {
                if (Console.KeyAvailable)
                {
                    Feed = Console.ReadLine();
                    if (Feed.ToUpper() == "/Q")
                    {
                        if (!Hosting)
                        {
                            Disconnect();
                            disconnetDone.WaitOne();
                        }
                        Quit = true;
                        Feed = string.Empty;
                    }
                    else if (Feed != string.Empty)
                    {
                        Feed += "<EOF>";
                    }
                }
                ProcessConnection();
            }
            else
            {
                ServerClosed();
            }
        }

        public void Host(IPEndPoint self)
        {
            Reset();
            Hosting = true;
            ID = self;
            if (ThisClient.clientSocket == null)
            {
                SetupClientSocket();
            }
            ThisClient.clientSocket.BeginConnect(ID, new AsyncCallback(ConnectCallback), ThisClient.clientSocket);
            connectDone.WaitOne();
        }

        private void ProcessConnection()
        {
            //what I want it to do is read asynchronously if data is available, and write the data asynchronously to the client after reading is done, and if the
            //client is closing or disconnecting, it will close the client's connection.

            if (!Quit)
            {
                if (!Connected(ThisClient.clientSocket) && ThisClient.clientSocket != null)
                {
                    ServerClosed();
                }
                else
                {
                    if (Feed != string.Empty)
                    {
                        Send(ThisClient, Feed);
                        sendDone.WaitOne(100);
                        sendDone.Reset();
                    }
                    if (ThisClient.ReadFeed.DataAvailable)
                    {
                        Receive();
                        receiveDone.WaitOne(100);
                        receiveDone.Reset();
                    }
                    Feed = string.Empty;
                }
            }
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket client = (Socket)ar.AsyncState;

                // Complete the connection.
                client.EndConnect(ar);
                ThisClient.Set(client);

                // Signal that the connection has been made.
                connectDone.Set();
                AttempingCon = false;
                Started = true;
            }
            catch (Exception e)
            {
                Console.WriteLine("The server is not responding.\n");
                AttempingCon = true;
                Started = false;
                //Console.WriteLine(e.ToString());
            }
        }

        private void ConnectIssueCallback(IAsyncResult result)
        {
            if (Connected(ThisClient.clientSocket))
            {
                try
                {
                    Client state = (Client)result.AsyncState;
                    NetworkStream handler = state.ReadFeed;
                    state.feedBack = string.Empty;

                    int bytesRead = handler.EndRead(result);

                    state.sb.Append(Encoding.ASCII.GetString(state.readBuffer, 0, bytesRead));
                    state.feedBack = state.sb.ToString();
                    if (state.feedBack == qr.CloseReason(ReasonForQuit.ServerFull))
                    {   //data transfer done
                        state.sb.Clear();
                        issueCheckDone.Set();
                        Console.WriteLine("Can't connect to the server. Server is full.");
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }
        }

        private void Receive()
        {
            if (Connected(ThisClient.clientSocket))
            {
                try
                {
                    // Begin receiving the data from the remote device.
                    ThisClient.ReadFeed.BeginRead(ThisClient.readBuffer, 0, BufferSpace.BufferSize,
                        new AsyncCallback(ReadStream), ThisClient);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }
        }

        private void ReadStream(IAsyncResult result)
        {
            if (Connected(ThisClient.clientSocket))
            {
                try
                {
                    Client state = (Client)result.AsyncState;
                    NetworkStream handler = state.ReadFeed;
                    state.feedBack = string.Empty;

                    int bytesRead = handler.EndRead(result);

                    if (bytesRead > 0)
                    {
                        state.sb.Append(Encoding.ASCII.GetString(state.readBuffer, 0, bytesRead));
                        state.feedBack = state.sb.ToString();
                        if (state.feedBack.IndexOf("<EOF>") > -1)
                        {   //data transfer done
                            string temp = state.feedBack.Remove(state.feedBack.Length - 5, 5);
                            state.feedBack = temp;
                            //Console.WriteLine("Recieved {0} bytes from {1}:{2}. Message: {3}", state.feedBack.Length, state.ID.Address,
                                //state.ID.Port, state.feedBack);
                            Console.WriteLine("Recieved message from {0}:{1}. Message: {2}", state.ID.Address,
                                state.ID.Port, state.feedBack);
                            state.sb.Clear();
                            receiveDone.Set();
                        }
                        else
                        {   //not all data sent
                            handler.BeginRead(state.readBuffer, 0, BufferSpace.BufferSize, new AsyncCallback(ReadStream), state);
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }
        }

        private void WriteStream(IAsyncResult result)
        {
            if (Connected(ThisClient.clientSocket))
            {
                try
                {
                    // Retrieve the socket from the state object.
                    Client state = (Client)result.AsyncState;
                    NetworkStream handler = state.WriteFeed;

                    // Complete sending the data to the remote device.
                    handler.EndWrite(result);

                    sendDone.Set();
                    Feed = string.Empty;
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }
        }

        private void Send(Client c, string data)
        {
            if (Connected(ThisClient.clientSocket))
            {
                try
                {
                    byte[] byteData = Encoding.ASCII.GetBytes(data);
                    c.WriteFeed.BeginWrite(byteData, 0, byteData.Length, new AsyncCallback(WriteStream), c);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }
        }

        public void AttemptConnect()
        {
            string i = string.Empty;

            i = Console.ReadLine();
            switch (i.ToUpper())
            {
                case "/R":
                    Console.WriteLine("Attempting to connect to the server...\n");
                    StartConnection();
                    break;
                case "/C":
                    Console.WriteLine("Returning to Main...\n");
                    AttempingCon = false;
                    break;
                default:
                    Console.WriteLine(PFInvald);
                    break;
            }
            i = string.Empty;
        }

        private void StartConnection()
        {
            if (ThisClient.clientSocket == null)
            {
                SetupClientSocket();
            }
            ThisClient.clientSocket.BeginConnect(ID, new AsyncCallback(ConnectCallback), ThisClient.clientSocket);
            connectDone.WaitOne(ConTryTimeOut);
        }

        private void CheckForIssues()
        {
            if (Connected(ThisClient.clientSocket))
            {
                if (ThisClient.ReadFeed.DataAvailable)
                {
                    ThisClient.ReadFeed.BeginRead(ThisClient.readBuffer, 0, BufferSpace.BufferSize, new AsyncCallback(ConnectIssueCallback), ThisClient);
                    Started = false;
                    Quit = true;
                    issueCheckDone.WaitOne();
                }
            }
        }

        private bool Connected(Socket s)
        {
            if (s != null)
            {
                return !((s.Poll(1000, SelectMode.SelectRead) && (s.Available == 0)) || !s.Connected);
            }
            return false;
        }

        private void Disconnect()
        {
            try
            {
                ThisClient.clientSocket.BeginDisconnect(false, new AsyncCallback(DisconnectCallBack), ThisClient);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private void ServerClosed()
        {
            if (!Hosting)
            {
                Console.WriteLine("Server connection stopped. Returning to main...\n");
            }
            ThisClient.ReadFeed.Close();
            ThisClient.WriteFeed.Close();
            if (ThisClient.clientSocket != null)
            {
                ThisClient.clientSocket.Close();
            }
            ThisClient.Set(null);
            Quit = true;
        }

        private void DisconnectCallBack(IAsyncResult result)
        {
            try
            {
                Client c = (Client)result.AsyncState;
                c.clientSocket.EndDisconnect(result);

                c.WriteFeed.Close();
                c.ReadFeed.Close();
                c.clientSocket.Close();
                c.Set(null);

                Console.WriteLine("Disconnected from the server. Returning to main...\n");
                disconnetDone.Set();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        public void StopConnection()
        {
            if (ThisClient.clientSocket != null)
            {
                if (ThisClient.clientSocket.Connected)
                {
                    ThisClient.WriteFeed.Close();
                    ThisClient.ReadFeed.Close();
                    ThisClient.clientSocket.Close();
                }
                connectDone.Close();
                sendDone.Close();
                receiveDone.Close();
                disconnetDone.Close();
                issueCheckDone.Close();
            }
            ThisClient.clientSocket = null;
        }
    }
}
 
// server code for console chat program
using System;
using System.Net;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.IO;
using System.Threading;
using MiscStuff;

namespace ServerTest
{
    using qr = MiscStuff.QuitReason;

    public class AsyncServer
    {
        private Socket serverSocket;
        private int maxConnections = 0;
        private List<Client> connections;
        private int endedConnections = 0;
        private bool AttemptingAccept = false;

        public AsyncServer(int maxC)
        {
            maxConnections = maxC;
        }

        private void SetupServerSocket(IPEndPoint id)
        { // Resolving local machine information
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(id);
            serverSocket.Listen(100);
            Console.WriteLine("Hosting started");
        }

        public void Start(IPEndPoint id)
        {
            connections = new List<Client>();
            AttemptingAccept = false;
            SetupServerSocket(id);
        }

        public void RunServer()
        {
            if (!AttemptingAccept)
            {
                AttemptingAccept = true;
                serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), serverSocket);
            }
            ProcessConnections();
        }

        public void Host(IPEndPoint ip)
        {
            Start(ip);
            serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), serverSocket);
        }

        private void ProcessConnections()
        {
            // process the connections and then remove ones that are to be closed from dcing or because the server is full.
            lock (connections)
            {
                foreach (Client c in connections)
                {
                    if (!c.toRemove)
                    {
                        if (!Connected(c.clientSocket))
                        {
                            CloseConnection(c); // sets a connection to close after all connections have been processed.
                        }
                        else
                        {
                            if (c.ReadFeed.DataAvailable)
                            {
                                c.ReadFeed.BeginRead(c.readBuffer, 0, BufferSpace.BufferSize, new AsyncCallback(ReadStream), c);
                            }
                        }
                    }
                }
            }
            if (endedConnections > 0)
            {
                for (int i = 0; i < connections.Count; i++)
                {
                    if (connections[i].toRemove)
                    {
                        connections.RemoveAt(i);
                    }
                }
                endedConnections = 0;
            }
        }

        private void AcceptCallback(IAsyncResult result)
        {
            try
            {
                // Finish Accept
                Socket s = (Socket)result.AsyncState;
                Client ConTest = new Client(null);
                ConTest.Set(s.EndAccept(result));
                if (ConTest.clientSocket != null)
                {
                    AttemptingAccept = false;
                    if (connections.Count == maxConnections) //server is full send a message to the client that its full so it can notify the user that its full
                    {
                        string reason = qr.CloseReason(ReasonForQuit.ServerFull);
                        Send(ConTest, reason);
                        Console.WriteLine("Client {0}:{1} tried to connnect but the server is full.", ConTest.ID.Address, ConTest.ID.Port);
                    }
                    else // server isn't full continue as normal
                    {
                        lock (connections)
                        {
                            connections.Add(ConTest);
                        }
                        Console.WriteLine("Client {0}:{1} connected. Total connected clients {2}", ConTest.ID.Address, ConTest.ID.Port, connections.Count);
                    }
                }
            }
            catch (Exception exc)
            {
            }
        }

        private void ReadStream(IAsyncResult result)
        {
            try
            {
                Client state = (Client)result.AsyncState;
                NetworkStream handler = state.ReadFeed;
                state.feedBack = string.Empty;
                int bytesRead = handler.EndRead(result);

                if (bytesRead > 0)
                {
                    state.sb.Append(Encoding.ASCII.GetString(state.readBuffer, 0, bytesRead));
                    state.feedBack = state.sb.ToString();
                    if (state.feedBack.IndexOf("<EOF>") > -1)
                    {   //data transfer done
                        lock (connections)
                        {
                            foreach (Client c in connections)
                            {
                                if (TestForSend(c))
                                {
                                    Send(c, state.feedBack);
                                }
                            }
                        }
                        state.sb.Clear();
                    }
                    else
                    {   //not all data sent
                        handler.BeginRead(state.readBuffer, 0, BufferSpace.BufferSize, new AsyncCallback(ReadStream), state);
                    }
                }
            }
            catch(Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private bool TestForSend(Client c)
        {
            return (!c.toRemove && Connected(c.clientSocket));
        }

        private void WriteStream(IAsyncResult result)
        {
            try
            {
                // Retrieve the socket from the state object.
                Client state = (Client)result.AsyncState;
                NetworkStream handler = state.WriteFeed;

                // Complete sending the data to the remote device.
                handler.EndWrite(result);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private void Send(Client c, string data)
        {
            try
            {
                byte[] byteData = Encoding.ASCII.GetBytes(data);
                c.WriteFeed.BeginWrite(byteData, 0, byteData.Length, new AsyncCallback(WriteStream), c);
            }
            catch(Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private bool Connected(Socket s)
        {
            if (s != null)
            {
                return !((s.Poll(1000, SelectMode.SelectRead) && (s.Available == 0)) || !s.Connected);
            }
            return false;
        }

        private void CloseConnection(Client c)
        {
            try
            {
                if (c.clientSocket != null)
                {
                    c.WriteFeed.Close();
                    c.ReadFeed.Close();
                    c.clientSocket.Close();
                    c.toRemove = true;
                    ++endedConnections;
                    Console.WriteLine("Client {0}:{1} disconnected. Total connected clients {2}", c.ID.Address, c.ID.Port, connections.Count - endedConnections);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
               
        public void StopServer()
        {
            foreach (Client c in connections)
            {
                c.WriteFeed.Close();
                c.ReadFeed.Close();
                c.clientSocket.Close();
            }
            connections.Clear();
            serverSocket.Close();
            serverSocket = null;
            Console.WriteLine("Hosting stopped. Returning to main...\n");
        }
    }
}

Hi.

You can use you none static public ip to host your server on but the IP is only valid while your router is on for that day it could chage at any time but my test show mine stay's until I restart the router, not much of a problem for testing just allow a place to reset the IP in your app good for testing .

Heres a link to a post that is almost the same as what you want. 3rd post from the bottom is the steps you need to do.

Link

If you're just testing with friends, and you have access to your home router, you can typically set up port forwarding and host on your own computer. This is not recommended for times when you're not already talking to the user on the phone...

The easiest way to set up a simple server is to lease some virtual private server slot from Digital Ocean, Interserver, Amazon Web Services, Azure Cloud, Rackspace, or similar. Each of these "cloud" providers will grant you some amount of CPU/memory/bandwidth/diskspace and a command line interface (you get "root") for some small amount of dollars per month. (Amazon even lets you pay nothing for the first year for the first, micro-size instance.)
If you need more CPU/RAM/etc, you can pay more.

The next easiest way is to lease a "self-managed Root server" from a server leasing place like 1and1, ServerBeach, Rackspace, or similar. The difference here is that you get "bare metal" hardware, rather than a virtual slice. For games with twitch reactions, this is generally an improvement. For web sites and turn-based games, this is not necessary.

Finally, if you're growing past a few self-managed root servers, or past a lot of virtual server instances, you can buy your own hardware, rent space in a "cage" in a "co-location facility," negotiate one or more backbone ISP contracts, and start running your own data center (small or large.) This is not recommended for someone who's just starting out, and many successful games never need to move off of Amazon Web Services.
enum Bool { True, False, FileNotFound };

Ok I'll probably then go with the virtual server unless when I do my kickstarter for my game that I get enough funding (and interest) that I may look into the physical server renting.

As I was also wondering based on the code that I posted from my console chat program (networking code that I know at the moment) is that going to be enough to do a TCG, is it secure, and what do I have to do to ensure that people can't cheat, aside from encrypting the data that will be sent back and for between the server and client?

I am going to design my client so it only will of course draw everything so the player can interact with the game, as well as send messages that the server will interpret to what the player wants to do.

Encrypting has nothing to do with cheating. For people to not be able to cheat in a TCG, you have to keep the card inventory on the server, and keep all the rules/judging on the server, and just tell the client what's going on -- don't let the client tell the server anything about card ownership, availability, or outcomes.

You also need to properly secure the sessions -- make sure the session is authenticated, can't be hijacked by another player, make sure you store pasword hashes using a secure method (like bcrypt) etc. There are actually tons of rules that have been learned the hard way over the years by game developers, because players that are incented to cheat have lots of ways of doing so. The FAQ for this forum has some more links.

Finally, all this only makes sense to worry about if you actually have a game that players will care about. If nobody cares about your game (or even knows about your game,) then nobody will want to cheat, and any work you spent on that will go un-tested.
enum Bool { True, False, FileNotFound };

I read over the FAQ again and didn't see anything about how to make sure the sessions are secure and can't be hijacked. What do I need to learn to do that with what I already know?

To make sure sessions are secure and not hijacked, the simplest solution is to use TLS.
If you use HTTP for each turn, you should use HTTPS (or now HTTP2) and follow best practices for session cookies and cross-site request forgery prevention.
For proper authentication across multiple machines (assuming matchmaker, login database, and game servers are different) you can use the token-based scheme described in this link: http://www.mindcontrol.org/~hplus/authentication.html
enum Bool { True, False, FileNotFound };

Note that just because they cannot be hijacked does not mean you should trust the client.

TLS makes it harder for a stranger on the internet to intercept and modify another random person's traffic. It is pretty easy from an attacker to set up a machine to intercept between their own client and an intercepter, then from the intercepter on to you.

TLS is basically an armored delivery service that is mostly secure by itself. It ensures the package was delivered to a particular destination. If I trust my side and I trust your side then the armored delivery service is helpful. However, they deliver anywhere, including to the bad guys.

Bad guys can make malformed packets and hacked data and log their own traffic, passing it through the armored delivery services too. Bad guys can set up their own botnet of cheaters and connect securely. Bad guys can install their own aimbots and connect securely. Bad guys can introduce exploitable network messages and still connect securely.

This topic is closed to new replies.

Advertisement