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;
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):
add the following to enginegamenetserverQuery.cc (at line 102)
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):
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:
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:
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:
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 :)
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 :)
About the author
#42
Running dedicated servers using command line -prefs option ... the loading of default.cs and pref.cs files has been moved to onStart() in scriptsAndAssets/main.cs this causes them to be executed *after* the -prefs file.
To fix, find the below code (lines 73 to 81 inclusive) and move them to the top of the file just under the command on line 7 'loadDir("common"); '
08/14/2008 (10:16 am)
For anyone trying to use this with TGEA 1.7.1;Running dedicated servers using command line -prefs option ... the loading of default.cs and pref.cs files has been moved to onStart() in scriptsAndAssets/main.cs this causes them to be executed *after* the -prefs file.
To fix, find the below code (lines 73 to 81 inclusive) and move them to the top of the file just under the command on line 7 'loadDir("common"); '
// Defaults console values
exec("./client/defaults.cs");
exec("./server/defaults.cs");
// Preferences (overide defaults)
if (isFile("./client/prefs.cs"))
exec("./client/prefs.cs");
if (isFile("./server/prefs.cs"))
exec("./server/prefs.cs");
#43
10/14/2008 (8:38 pm)
Many thanks!
#44
I have added a section of code to provide an empty server list to the client should all of the servers time out, this *should* never happen....
however during a test of the resource on a clean install, I managed to add
with a hasty copy and paste, the compiler pointed out the missing semicolon so I added it, not noticing the missing 00 this caused all the servers in the list to be removed on the first query from the client, the master server then had no servers to return and client never got a response from the master server!
lesson leaned, ensure you always respond to client calls!
07/29/2011 (4:58 am)
Updated this resource to work with TGE 1.5.2 and with the new GG website.I have added a section of code to provide an empty server list to the client should all of the servers time out, this *should* never happen....
however during a test of the resource on a clean install, I managed to add
static const U32 gExampleHeartbeatTimeout = 1200
with a hasty copy and paste, the compiler pointed out the missing semicolon so I added it, not noticing the missing 00 this caused all the servers in the list to be removed on the first query from the client, the master server then had no servers to return and client never got a response from the master server!
lesson leaned, ensure you always respond to client calls!

Torque Owner Vis