Game Development Community

dev|Pro Game Development Curriculum

Add a built in master server

by Vis · 11/27/2005 (10:44 pm) · 44 comments

On a LAN the game clients find active servers via broadcast packets, however these broadcast packets cannot be used over the Internet so we arrange for the game servers to contact a master server, then when a game client wants a list of active servers it only needs to talk to the master server.

When a Torque game server is started it sends a heartbeat packet to the master server at Garage Games, the master server will send a GameMasterInfoRequest packet back and the game server then sends a GameMasterInfoResponse back, telling the master server all the details about itself (the master server will continue to request this infomation periodicly to keep its internal list upto date), a client can ask the master server to send a list of active game servers that meet certain requirements such as mission type or number of players, ect.

Seeing as the default game browser in the Torque client does not provide filtering, and to keep this example simple, we will create a master server that will return only a full list of all active game servers with no filtering.

(EDIT July 29 2011)
This resource was written for TGE 1.3
If using TGE 1.5.2 you will need to change line 6 in main.cs to read;

$defaultGame = "starter.fps";


First we create a structure to hold the address and the time we last got a heartbeat from the server, place the following in enginegamenetserverQuery.h after the structure for MasterInfo (line 35):

struct ExampleServerData
{
   NetAddress address; 
   U32 lastBeatTime;   
      
   ExampleServerData()
   {
   		lastBeatTime = 0;
   }
};

add the following to enginegamenetserverQuery.cc (at line 102)

Vector<ExampleServerData> gExampleServerList(__FILE__, __LINE__);
static const U32 gExampleHeartbeatTimeout = 120000;

Now we create two packet handler functions, the first, handleGameHeartbeat(), is sent by game servers on a regular basis, the default is every 2 minutes, and the master server uses it to keep its list uptodate. The second function handleMasterServerListRequest() is sent by game clients, and is used to get a list of active game servers.

Find the last function definition at the bottom of the file, add the following code above that function (at aound line 2014):

static void handleGameHeartbeat( const NetAddress* address, U32 key, U8 flags )
{
  // post message to console
  char netString[256];
  Net::addressToString(address, netString);
  Con::printf( "Received Heartbeat from [%s].", netString);
   
  // scan through the list of servers and find the sending server
  // then set its "last heartbeat value" to timenow
  // if the server is not in the list add it.
  U32 TimeNow = Sim::getCurrentTime();
  bool found = false;
   
  for ( U32 i = 0; i < gExampleServerList.size(); i++ )
  {
    // if this address is in the list update the time and flag it as found
    if(Net::compareAddresses(&gExampleServerList[i].address, address))
    {
       gExampleServerList[i].lastBeatTime = TimeNow;
       found = true;
       break;
    }
  }
   
  // if we did not find the address add it with the current time
  if(!found)
  {
    Con::printf( "Adding [%s] to list", netString);
    ExampleServerData update;

    update.address = *address;
    update.lastBeatTime = TimeNow;
    gExampleServerList.push_back(update);
  }
   
  // dump the current list to console,
  // this has only been included for development.
  Con::printf( "----- Current List ------");
  Con::printf( " Time now:  %u",TimeNow);
  for ( U32 i = 0; i < gExampleServerList.size(); i++ )
  {
    char tempString[256];
    Net::addressToString(&gExampleServerList[i].address, tempString);
    Con::printf( " %u. [%s] expires at %u ",
            i,
            tempString,
            gExampleServerList[i].lastBeatTime+gExampleHeartbeatTimeout);
  }
  Con::printf( "-------------------------");
}

static void handleMasterServerListRequest(const NetAddress* address,U32 key,U8 flags)
{
  // Rather than adding a house keeping function to find and remove inactive
  // servers it has been simpler to do it here, to ensure only an upto date 
  // list is sent to the client.

  // post message to console 
  char netString[256];
  Net::addressToString(address, netString);
  Con::printf( "Received MasterServerListRequest packet from [%s].", netString);

   
  // remove any servers where:
  // "time now" > "last heartbeat time" + "heart beat timeout"
  U32 TimeNow = Sim::getCurrentTime();   
  U32 i = 0;

  while(true)
  {
    // exit loop if we are out of range
    if(i >= gExampleServerList.size() )
      break;

    if (TimeNow > (gExampleServerList[i].lastBeatTime + gExampleHeartbeatTimeout))
    {
      // for development we are posting our removal of this server to console
      Net::addressToString(address, netString);
      Con::printf( "[%s] has timed out, and has been removed from the list",
                   netString);

      // this server has timed out so remove it from the list
      gExampleServerList.erase(i);
      continue;
    }

    ++i;
  }

  // Send each server to the calling network address
  // NOTE we *should* fill a packet with a list of servers but we
  // are sending a packet for each server.
  for(U32 i = 0; i < gExampleServerList.size() ; ++i)
  {
    BitStream *out = BitStream::getPacketStream();

    out->write( U8( NetInterface::MasterServerListResponse ) );
    out->write( U8(flags) );
    out->write( U32(key) );
    out->write( U8(i) ); // packet index
    out->write( U8(gExampleServerList.size()) );  // total number of packets
    out->write( U16(1) );  // servers in this packet
    out->write( U8(gExampleServerList[i].address.netNum[0]));  // server address
    out->write( U8(gExampleServerList[i].address.netNum[1]));  // server address
    out->write( U8(gExampleServerList[i].address.netNum[2]));  // server address
    out->write( U8(gExampleServerList[i].address.netNum[3]));  // server address
    out->write( U16(gExampleServerList[i].address.port));  // server port
      
     BitStream::sendPacketStream(address);
     
     Net::addressToString(address, netString); 
     Con::printf( "Sending server list to [%s].", netString ); 
  }

  
  // 2011 update, added the following code so that if no servers are in the list the client still gets told!
  if(gExampleServerList.size() == 0)
  {
    BitStream *out = BitStream::getPacketStream();  
  
    out->write( U8( NetInterface::MasterServerListResponse ) );  
    out->write( U8(flags) );  
    out->write( U32(key) );  
    out->write( U8(0) ); // packet index  
    out->write( U8(0) );  // total number of packets  
    out->write( U16(0) );  // servers in this packet  
        
    BitStream::sendPacketStream(address); 
     
    Net::addressToString(address, netString); 
    Con::printf( "Sending empty server list to [%s].", netString ); 
  }
}

We need to add case statments for our two new packet handlers to DemoNetInterface::handleInfoPacket() function, you will find it at the bottom of serverQuery.cc

Before the closing break on the switch statement, add the following:

case GameHeartbeat:
         handleGameHeartbeat( address, key, flags );
         break;
                 

    case MasterServerListRequest:
         handleMasterServerListRequest( address, key, flags );
         break;

compile the changes.

Now we are ready to test the changes, we can do this on a single workstation, using starter.fps.

We need to change the default master server that our build will use, open examplestarter.fpsserverdefaults.cs

Edit the master server address on line 9 from:

"2:master.garagegames.com:28002"; to "2:(your ip address here):28111"

example: "2:192.168.2.4:28111" (although your IP will likley be different!)
Note: do not use 127.0.0.1 because this address is filtered out by code in Net::process(), you must use your workstations IP

save the file.

To run multiple servers out of the same directory we need to create a different pref file for each server, make a copy of examplestarter.fpsserverdefaults.cs and name it master_prefs.cs

open it and make the following changes:

Change the server name from "FPS Starter Kit" to "Master Server" on line 12
Change the server port from 28000 to 28111 on line 28.

Now make 3 copies of master_prefs.cs and name them:
game_1_prefs.cs
game_2_prefs.cs
game_3_prefs.cs

open game_1_prefs.cs and change the server name (on line 12) to "Game Server 1" and the server port (on line 28) to 28101
open game_2_prefs.cs and change the server name (on line 12) to "Game Server 2" and the server port (on line 28) to 28102
open game_3_prefs.cs and change the server name (on line 12) to "Game Server 3" and the server port (on line 28) to 28103

now move up to the "example" directory and create a batch file named run_master.bat

open it and add this line:

torquedemo.exe -dedicated -prefs starter.fps/server/master_prefs.cs -mission starter.fps/data/missions/stronghold.mis

NOTE: check your executable name it may have been created as torqueDemo_DEBUG.exe, change the above line to reflect your executable name.

make three copies of this file and name them:
run_game_1.bat
run_game_2.bat
run_game_3.bat

Edit each file and change master_prefs.cs to game_1_prefs.cs, game_2_prefs.cs and game_3_prefs.cs respectively.

Start the master server with run_master.bat, once its up and running start up the three game servers with the other batch files.
the master server will list the new game servers as they are started, along with itself.

There is a problem that relates to the pref.cs file created when the servers are stopped, the client will use this file with the servers settings, to avoid this we need to force the client to use the default.cs file, the easiest way to do this is to start the client from a batch file that deletes the offending pref.cs files before running TorqueDemo.exe

create examplerun_client.bat

open it and paste the following:

del starter.fpsserverprefs.cs.dso
del starter.fpsserverprefs.cs
torquedemo.exe

Now delete the files:
starter.fpsclientprefs.cs.dso
starter.fpsclientprefs.cs

These files hold the old settings for the master server and deleting them will force the adoption of the new master server location.

start the client using run_client.bat

hit Join Server and then Query Master, all of your servers should be listed.

job done.

Note: it would be prudent to run your PC on a static IP for testing/development, it can be a pain to track down the problem when your DHCP and PC conspire to trip you up with a covert IP change :)

Page «Previous 1 2 3 Last »
#1
11/21/2005 (8:05 am)
Please be kind its my first resource :)
#2
11/28/2005 (12:36 am)
This is very cool! Does it work with 1.4? I guess I'll try it and see when I get some time.
#3
11/28/2005 (12:02 pm)
thx Midhir, I have not tested on 1.4, but it works on TSE, let us know how you got on ....
#4
11/29/2005 (7:32 pm)
There are issues with 1.4, I will find a solution soon as I get time .....
#5
11/30/2005 (10:38 am)
I know it's possible, but how hard would it be to port this into a self contained application?
#6
11/30/2005 (7:44 pm)
Great ReSource!
VIS,Thanks you very much!

This Helps me a lot!
#7
12/02/2005 (12:14 pm)
Nice work Vis. One flaw I see is that other clients have to know the IP address of your server which is not too usefull if Joe Blow wants to host a game and get clients to join when they don't have Joe Blows IP address.

How do the peer-to-peer, file sharing crowd do it? It would be nice if clients could get broadcasts from all current hosts in a similar way which eliminates the need for a master server and removes some stress from the GG staff.
#8
12/02/2005 (6:50 pm)
@Duncan,

All the clients you distribute with this change will have the IP (or URL) defined in \example\starter.fps\server\defaults.cs so the "other clients" will already know the correct IP for the master server this is how the standard build works.

All this resource provideds is a way for a "standard" game server to also act as a master server, there are many reasons why this is not the best solution, (sledge hammers and nut cracking spring to mind) but for development its a simple option that I have found works well.

@Joshua,
For a self contained application have a look at www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5962 its where I started before developing this resource.

@Midhir
Currently there seems to be problems with running dedicated servers under 1.4, however I suspect that once these issues have been resolved this resource should work with 1.4
#9
12/08/2005 (3:05 am)
very nice! Vis
#10
12/13/2005 (1:34 am)
my compliments, and appreciation... good to see people putting in the effort to help each other out.. keep up the great work.. and again.. ty..

;^)_-~~ -V,,
#11
12/13/2005 (1:42 am)
oh..and let me know when ready for 1.4.. heheheh

---o00o--
#12
01/25/2006 (9:05 pm)
I'm about to add this to a 1.4 for testing purposes. i was wondering if anyone knew if this worked w/ 1.4 yet, and if not what kind of trouble it had? did the network protocol change from 1.3 to 1.4 or like many other resources did the header file locations just move?
#13
01/26/2006 (12:02 am)
The only problems with 1.4 related to running dedicated servers, the base networking code in 1.4, as far as I can tell, has not changed.
#14
05/21/2006 (1:35 am)
:plz delete:
#15
05/21/2006 (2:31 am)
This resource does not work with 1.4 - sorry.

I've not had time to look at this yet.
#16
05/21/2006 (3:43 am)
Very good but I can confirm that this does not work with 1.4
#17
08/26/2006 (2:59 pm)
If any one has updated this for 1.4 please let us know :D
#18
09/24/2006 (11:20 am)
Any updates to this?

Also, anyone that has used this with 1.3, what is the load capacity? If you use TGE and this resource to act only as the master server, how many games could it handle?
#19
01/26/2007 (7:21 pm)
*BUMP*
PBMS doesn't seem to work with TGE 1.4 or 1.5 either.
What is GG using??
#20
01/26/2007 (8:08 pm)
Thats a good question and one I wouldn't mind having answered aswell. We all need a master server for our games after all.
Page «Previous 1 2 3 Last »