• Advertisement
Sign in to follow this  

Setting up a Server for a Multiplayer Game

This topic is 1063 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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.

Edited by Oconzer

Share this post


Link to post
Share on other sites
Advertisement

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");
        }
    }
}
Edited by Oconzer

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

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?

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

Thank you that helps me greatly for when I get to making the networking part of my game.

Share this post


Link to post
Share on other sites

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.

 

A bit confused on what you mean by this. Do you mean that despite me setting up my server program (when i get there that is) to have only certain things as acceptable messages for controlling the game from the client side that they can cheat / create their own rules???

Edited by Oconzer

Share this post


Link to post
Share on other sites


Do you mean that despite me setting up my server program (when i get there that is) to have only certain things as acceptable messages for controlling the game from the client side that they can cheat / create their own rules???

Yes, they can still cheat.

 

Encryption only protects data in transit.

 

Example:  Encryption protects the data between the customer and the bank. Encryption also protects the data between the bad guy and the bank. Encryption does not prevent a bad guy from opening a web browser in a debugger, opening a secure connection, and telling the bank to transmit millions of dollars from one account to another. Encryption only protects from evesdroppers.

 

Another example: Encryption protects data from the sender and receiver. Encryption does not prevent either side from doing anything with the data once it is there.  The sender can potentially send invalid data, wrong addresses, stolen credit card numbers, or any other bad data, the other side still needs to validate it.

 

Another example: Encryption means that once the game sends the data off to you the data is more secure in transit. However, the player on their own computer can perform a very simple man-in-the-middle attack. (Many corporate networks install a man-in-the-middle certificate for HTTPS as a standard policy, it is quite easy.)  The game can transmit data to the intermediate machine, the intermediate machine can make a minor adjustment to the data such as modifying a score, and then encrypt it and send it off to the server. 

 

Another example: Encryption between end points does not protect your executable. A player could attach debugging tools or scripts to your program. Perhaps there is a rare powerful card in the deck. If the client is in charge of their own deck an attacker could modify the shuffle to guarantee the card is present every time. If your protocol lets the client tell what cards are in the hand, the player could play the powerful card every single turn twenty turns in a row even if you intended for there only to be one of those cards. Encryption does not protect against any invalid data.

 

Another example: Several online attackers have found secret debug commands in the protocols for things like an instant-kill, or commands for moderators to kick or ban players. They modify the program to transmit the commands over the secure connection. Encryption will not protect from these commands.

 

 

In all cases your server must control the data from beginning to end. Your cards must be shuffled on the server and you tell the player what cards are in their hand. When the player says they take an action with a card you need to verify that they really have the card in their hand and that the action is valid at that time. If the player says to transfer something from one account to another you need to verify that the items are present in both and the player has permission to do the transfer, as well as validating that the transfer meets any rules on transfers. If you have an event like a moderator kicking/banning a player you need to verify that the sender has permissions to actually perform the actions. Even if your communication is encrypted you need to validate that every command and event is valid, that the target of the command or event is valid, that the account is authorized to do the command or action, and otherwise validate every aspect of the content.

Share this post


Link to post
Share on other sites


In all cases your server must control the data from beginning to end. Your cards must be shuffled on the server and you tell the player what cards are in their hand. When the player says they take an action with a card you need to verify that they really have the card in their hand and that the action is valid at that time. If the player says to transfer something from one account to another you need to verify that the items are present in both and the player has permission to do the transfer, as well as validating that the transfer meets any rules on transfers. If you have an event like a moderator kicking/banning a player you need to verify that the sender has permissions to actually perform the actions. Even if your communication is encrypted you need to validate that every command and event is valid, that the target of the command or event is valid, that the account is authorized to do the command or action, and otherwise validate every aspect of the content.

 

OK. I had posted earlier that I was going to only have the client responsible for drawing the information that is necessary for interacting with the game, as well as sending what the client wants to do to the server. The server was going to interpret what the user wants to do based on what state the game has that player on the server (basically checks to make sure that if they are in a game that they can't trade with a player for example or edit their deck, etc). If this was what you were meaning, I was already planning on making sure that the player can't do something that they aren't supposed to be able to do unless the server says its fine.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement