Game server list

BlitzMax Forums/BlitzMax Programming/Game server list

JoshK(Posted 2008) [#1]
Does anyone have any example code that sets up a central server list, where you can add and remove servers like in the old Blitz3D gnet?


Dreamora(Posted 2008) [#2]
import brl.gnet


or a simple php based meta server to which you send http 1.1 requests, code for this should be available more than enough I think :)


JoshK(Posted 2008) [#3]
The BlitzMax gnet module has nothing to do with what I am asking.

If code for this is so plentiful, why bother posting a response telling me you think it is 'available'?


Helios(Posted 2008) [#4]
Well the method I use is to have a simple php script on a web server which each server can simply make requests to, to add and remove themselfs from the list (they also need to send requests every 50 seconds or so (this can be changed) otherwise they get removed from the list. This is almost exactly the same method that GNet uses, the only real difference is the following script is somewhat more expanded .

<?

	// Master Server
	// Copyright 2008 Binary Phoenix

	// This script is pretty simple, it allows you to pass an argument to this file that
	// specifies what statistic you want. Normally this will be a request to get all
	// servers running for a given game.
	
	// MySql DB Structure;
	//
	//  - servers (table)
	//		- ID, int, auto_increment, primary key
	//		- IP, tinytext
	//		- Name, text
	//		- TimeoutTimer, int
	//		- GameName, text
	
	// Arguments;
	//
	//		- action : This specifies the action to preform, it can be any of the following;
	//			- listservers  :  This will cause the script to emit a list of currently
	//							 running servers. This must be used with the gameid argument
	//							 to specify what game the servers must be running.
	//			- addserver    : This will add the current ip to the server list. This must be 
	//							 used with the gameid argument to specify what game the server is be running.
	//			- removeserver : This will remove the current ip from the server list. 
	//			- refreshserver: Resets the servers timeout timer so that its not removed from
	//							 the server list.
	//
	//		- gamename 		: This is used by several actions to decide what game the servers it 
	//				   		  is dealing with should be running.
	//		- servername 	: This is used when adding a server to the list. It just contains
	//					   	  a name based description of the server.
	//		- ip			: Returns the current ip address.
	//			
	
	// Errors;
	//	
	//		The server can return certain error strings depending on the situation, what follows
	//		is a list of them;
	//
	//		- "error 1" : This is a mysql error. Its usually temporary.
	//		- "error 2" : Argument missing. This occurs when the request url dosen't 
	//					  contain an required argument.

	// This variable stores how long it takes for a server to timeout.
	$timeoutDelay = 60; // 1 minutes.
	
	// Connect to the database.
	$connection = mysql_connect("localhost", "InsertDataBaseUsername", "InsertDataBasePassword") or die("error 1");
	mysql_select_db("InsertDataBaseName") or die("error 1");

	// Make sure we have been given an action.
	if (isset($_GET['action']))
	{
		// Process the action.
		switch ($_GET['action'])
		{
			case 'ip':
			
				die($_SERVER['REMOTE_ADDR']);
				break;

		
			case 'listservers':
				
				// Check correct arguments have been passed.
				if (!isset($_GET['gamename'])) die("error 2");
				
				// Get required arguemnts and clean them up.
				$gamename = $_GET['gamename'];
			
				// Kill off any servers that haven't refreshed themselfs in a long time.
				mysql_query("DELETE FROM servers WHERE TimeoutTimer <= " . time());
				
				// Grab server list.
				$result = mysql_query("SELECT * FROM servers WHERE GameName='" . mysql_escape_string($gamename) . "'");
				
				if ($result)
				{
					// Emit all the servers.
					while ($row = mysql_fetch_assoc($result)) 
					{
						echo $row['IP'] . '|' . $row['Name'] . "\n";
					}
				}
				
				break;
				
			case 'addserver':
				
				// Check correct arguments have been passed.
				if (!isset($_GET['gamename'])) die("error 2");
				if (!isset($_GET['servername'])) die("error 2");
				
				// Get required arguemnts and clean them up.
				$gamename = $_GET['gamename'];
				$serverName = $_GET['servername'];
				
				// Remove any old servers with the same ip and name.
				mysql_query("DELETE FROM servers WHERE IP='" . mysql_escape_string($_SERVER['REMOTE_ADDR']) . "' and Name='" . mysql_escape_string($serverName) ."' and GameName='" . mysql_escape_string($gamename) . "'");
				
				// Insert it into the database.
				mysql_query("INSERT INTO servers(IP, Name, TimeoutTimer, GameName) VALUES('" . mysql_escape_string($_SERVER['REMOTE_ADDR']) . "','" . mysql_escape_string($serverName) . "'," . (time() + $timeoutDelay) . ",'" . mysql_escape_string($gamename) . "')");

				break;
				
			case 'removeserver':
			
				// Remove this server based on its ip.
				mysql_query("DELETE FROM servers WHERE IP='" . mysql_escape_string($_SERVER['REMOTE_ADDR']) . "'");
			
				break;

			case 'refreshserver':
			
				// Refersh the servers timeout timer.
				mysql_query("UPDATE servers SET TimeoutTimer='" . (time() + $timeoutDelay) . "' WHERE IP='" . mysql_escape_string($_SERVER['REMOTE_ADDR']) . "'");
			
				break;
		}
	}
	else
	{
		die("error 2");
	}

	// Close database connection.
	mysql_close($connection);

?>


Within blitzmax all you need to do to make requests to this script, is to include the brl.httpstream module and open a stream to the script using the correct parameters and read in the first line (which will be an error number if something has gone wrong).

For example to add a server you would open a stream to;

local stream:TStream = OpenStream("http::www.YourDomain.com/script.php?action=addserver&gamename=YourGameName&servername=YourServerName")
local result:string = ReadLine(stream)
CloseStream(stream)


And so forth. Hope that helps. Not sure if the code will work I haven't tested it with blitz yet.


nawi(Posted 2008) [#5]
Well, for my game I just coded a simple server, that game servers connect to and update information. If there is no more information in n seconds, the server is deleted from the list. Clients also connect to this server to request the list. If you know how networking works you can figure out the rest..


Dreamora(Posted 2008) [#6]
Well you asked for something simple, so why not use GNet.

And the sources are really available like sand at a beach. Its one of the most common questions on BM and Blitz3D on how to do the communication between Blitz and PHP and a metaserver list shouldn't be a problem. the quakenet php - mysql tutorial already covers far more than you would need for that.


JoshK(Posted 2008) [#7]
Thank you we are looking at the code above.


JoshK(Posted 2008) [#8]
I was able to connect and add a server, but was unable to retrieve a list of servers. It just prints blank lines:
Local stream:TStream

stream=OpenStream("http::www.leadwerks.com/gameservers/game1.php?action=addserver&gamename=my_game&servername=my_server")
If Not stream RuntimeError "ERROR"
While Not stream.Eof()
	Print ReadLine(stream)
Wend
stream.close()

stream=OpenStream("http::www.leadwerks.com/gameservers/game1.php?action=listservers&gamename=my_game")
If Not stream RuntimeError "ERROR"
While Not stream.Eof()
	Print ReadLine(stream)
Wend
stream.close()



degac(Posted 2008) [#9]
Try this http://www.blitzbasic.com/codearcs/codearcs.php?code=2208 to manage GET/POST on http streams.

If http.Open("www.leadwerks.com") 
	'POST Example

result$=http.Post("gameservers/game1.php",["action","gamename","servername"], ["addserver","my_game","my_server"] ) 

End If


The 'logic' is (for POST method)
http.Open (your MAIN domain = www.leadwerks.com)
result = http.Post (path_of_your_page,[array_of_variables],[array_of_values])

I've tested and got the following result

HTTP/1.1 200 OK
Date: Sat, 13 Sep 2008 19:10:35 GMT
Server: Apache
Connection: close
Content-Type: text/html

error 2



This one for the GET method

If http.Open("www.leadwerks.com") 
result$=http.Get("gameservers/game1.php?action=addserver&gamename=my_game&servername=my_server")
end if



HTTP/1.1 200 OK
Date: Sat, 13 Sep 2008 19:20:15 GMT
Server: Apache
Connection: close
Content-Type: text/html

Process complete



I dont' know if it is the result I should expect, but there is no error 404.


JoshK(Posted 2008) [#10]
I did not set up the table.

OMG it werks!


JoshK(Posted 2008) [#11]
Ultimately all this does is retrieve a list of server IPs. Therefore it can be completely separate from whatever networking code you might use. My goal is to make a module that only adds, removes, refreshes, and retrieves a list of servers. Then it can be used with whatever networking system you want.

This all works, but it opens a new stream each time an action is performed. This can take hundreds of milliseconds. Does anyone know how I can write to and from the stream to send new messages? I looked at BlitzHTTPGet but if I use more than one Get command it crashes:

If http.Open("www.google.com") 
	'POST Example
	Print http.Post("/search", ["hl", "q", "btnG"] , ["en", "fantasaar", "Google Search"] )
	Print http.Post("/search", ["hl", "q", "btnG"] , ["en", "fantasaar", "Google Search"] ) 
End If
http.Close()


If it takes 300 millisecs to refresh the server every minute, that is not usable.

SuperStrict

'=========================================
'test
Local s$
Local servers$[]
Local server:TPubServer

server=TPubServer.Create("http::www.leadwerks.com/gameservers/game1.php","My_Server","My_Game")
servers=TPubServer.list("http::www.leadwerks.com/gameservers/game1.php","My_Game")
If servers
	For s=EachIn servers
		Print s
	Next
EndIf

'=========================================


Type TPubServer
	
	Field url$
	Field stream:TStream
	
	Method Delete()
		Remove()
	EndMethod

	Function Create:TPubServer(url$,servername$,gamename$="")
		Local stream:TStream
		Local server:TPubServer
		server=New TPubServer
		server.url=url
		server.stream=OpenStream(url+"?action=addserver&gamename="+gamename+"&servername="+servername)
		If Not server.stream Return Null
		While Not server.stream.Eof()
			If ReadLine(server.stream).Trim()
				server.stream.close()
				Return Null
			EndIf
		Wend
		Return server	
	EndFunction

	Method Refresh:Int()
		stream=OpenStream(url+"?action=refreshserver")
		If Not stream Return False
		stream.close()
		Return True	
	EndMethod
	
	Method Remove:Int()
		stream=OpenStream(url+"?action=removeserver")
		stream.close()
		stream=Null
		Return True	
	EndMethod

	Function List:String[](url$,gamename$="")
		Local descarray:String[]
		Local list:TList
		Local stream:TStream
		Local s$,sarr$[],n:Int
		stream=OpenStream(url+"?action=listservers&gamename="+gamename)
		If Not stream Return Null
		list=New TList
		While Not stream.Eof()
			s=ReadLine(stream)
			If s.contains("|")
				list.addfirst(s)
			EndIf
		Wend
		stream.close()
		If list.IsEmpty() Return Null
		descarray=New String[list.count()]
		For s=EachIn list
			descarray[n]=s
			n:+1
		Next
		Return descarray
	EndFunction
	
EndType



Helios(Posted 2008) [#12]
Sadly thats one of the problems with the method I suggested. Its never been much of a problem for me as small delays aren't really noticable
in the games I've been using it for. However there are a few things I can think of off the top of my head that might help;

- Remove Refresh Code
Simplest and crudest method. Remove the refresh code, or reduce the timeout interval. Might cause problems with dead servers though.

- Cron-Job
Setup a cron-job on the computer hosting the master server script. Make the cron-job ping all game servers every few minutes and remove the
servers that don't respond. This transfers all the connection delays onto the master server instead of the game.

- Seperate Thread
Most obvious one really. Put all the refrehs code on a seperate thread. Not sure how well BMax would handle this though with its lack of threading support.

- Connection: Keep-Alive
When requesting data from the server, send a keep-alive connection header with the HTTP request. Then send all requests down the same socket. This removes all the connection junk that slows everything down.


Ked(Posted 2008) [#13]
Not sure how well BMax would handle this though with its lack of threading support.

Hmmm...


Helios(Posted 2008) [#14]
Yes I noticed the thread bmax topic just after posting this :). I'm rather out of date I'm afraid.


JoshK(Posted 2008) [#15]
Okay, here is my code:


And an example of usage:


The purpose of this code is to retrieve a list of available server IP addresses. So I see this as something that can be completely separate from networking commands, and this could be used with any networking lib. It would be more useful if the Refresh() method could be run on a separate thread, but I don't know enough about threading yet to do that.