DaemonForums  

Go Back   DaemonForums > Miscellaneous > Guides

Guides All Guides and HOWTO's.

Reply
 
Thread Tools Display Modes
  #1   (View Single Post)  
Old 27th June 2010
Carpetsmoker's Avatar
Carpetsmoker Carpetsmoker is offline
Real Name: Martin
Tcpdump Spy
 
Join Date: Apr 2008
Location: Netherlands
Posts: 2,243
Default Online Unreal Tournament server browser with pcntl_fork()

Another Unreal Tournament related script that's I’ve been using for a long time.

Today I refactored the code to use pcntl_fork() in order to speed the script up.
If you want to any form of multiprocessing in PHP, you have little choice but to use pcntl_fork(), this function call and almost all other pcntl* and posix* calls are essentially the same as the C calls—PHP is such a “high-level” language…

Do not use exit() to exit the child process, use posix_kill(getmypid, SIGKILL) instead. For reasons not clear to me exit() doesn't work when run from CGI, it exits the parent process too (It does work when run from the commandline).

In action
Here are working demo’s:
ut-serverbrowse with fork()
Without fork()

The code with fork
Code:
  <?php
  #
  # Martin Tournoij <martin@arp242.net>
  # Free for any use. There are no restrictions.
  #
  # Note: We use pcntl_fork(), you will need the pcntl PHP module.
  # Also note this will not work in Windows or if PHP is loaded as Apache module.
  #
  
  function Query($server, $cmd)
  {
  	# http://wiki.beyondunreal.com/Legacy:UT_Server_Query
  
  	$address = gethostbyname($server['0']);
  	
  	if (!$s = fsockopen('udp://' . $server['0'], $server['1'], $errno, $errstr, 2))
  		return False;
  
  	stream_set_timeout($s, 2);
  	fputs($s, "\\$cmd\\");
  	$info = fread($s, 8192);
  	fclose($s);
  	$info2 = explode('\\', "\\$info\\");
  
  	foreach ($info2 as $k => $v)
  	{
  		if (empty($v) || $v == '' || $v == ' ')
  			continue;
  		if (is_int($k/2))
  			$pv = $v;
  		else
  			$prettyinfo["$pv"] = htmlentities($v);
  
  	}
  	$prettyinfo['str'] = $info;
  
  	return $prettyinfo;
  }
  
  function Server($s)
  {
  	$info = Query($s, 'status');
  
  	if (empty($info['str']))
  	{
  		# Just hide the entry, servers seem to be going up & down more often than 
  		# the space shuttle ...
  		#return "<tr><td colspan=\"4\">Could not connect to {$s['0']} {$s['1']}</td></tr>";
  		return '';
  	}
  	
  	if (isset($info['numplayers']))
  	{
  		$numplayers = $info['numplayers'] . " Players:<br />\n";
  		$players = Query($s, 'players');
  
  		for ($i=0; $i<=$info['maxplayers']; $i++)
  		{
  			$k = "player_$i";
  			if ($players["$k"])
  				$numplayers .= $players["$k"] . "<br />\n";
  		}
  	}
  	else
  		$numplayers = 'No players';
  
  	# XXX Detect DTAS in a better way
  	if ($info['gametype'] == 'INFCoopUnrealGame')
  		$gametype = 'Coop';
  	elseif ($info['gametype'] == 'INFg_EASGame')
  		$gametype = 'EAS';
  	elseif (stristr($info['hostname'], 'dtas'))
  		$gametype = 'DTAS';
  	elseif ($info['gametype'] == 'InfilTeamGamePlus')
  		$gametype = 'TDM';
  	else
  		$gametype = $info['gametype'];
  
  	$row = sprintf('<tr>
  		<td><a href="ut-serverbrowse.php?server=%s:%s">%s</a><br />
  		<span class="small">%s:%s</span></td>
  		<td>%s</td>
  		<td>%s</td>
  		<td>%s</td>
  		</tr>',
  		$s['0'], $s['1'], $info['hostname'], $s['0'], $s['1'], $info['mapname'], $gametype, $numplayers);
  
  	return $row;
  }
  
  function html($tbl)
  {
  	if (stristr($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml'))
  		$charset = 'application/xhtml+xml; charset=utf-8';
  	else
  		$charset = 'text/html; charset=utf-8';
  	header("Content-Type: $charset");
  
  	print '<?xml version="1.0" encoding="UTF-8"?>';
  	printf('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  			"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
  	<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
  		<head>
  			<meta http-equiv="Content-Type" content="%s" /> 
  			<title>Infiltration Servers</title>
  			<style type="text/css">
  				table { border-collapse: collapse; margin: .25em; width: 95%%; }
  				td, th, table, caption { border: 1px solid black; }
  				tfoot td { border: none; font-size: .8em; }
  				caption, th, tfoot { background-color: #ddd; }
  				caption { font-weight: bold; }
  				tr:hover { background-color: #eee; }
  				div { border: 1px solid black; margin: .25em; width: 94%%; padding: .2em; margin-top: 2em; }
  				label { width: 6em; }
  				.small { font-size: .75em; margin-left: 2em; }
  				p { width: 40em; }
  			</style>
  		</head>
  		<body>
  			<p>Refresh your browser and the list will be refreshed. Click on a link to 
  				refresh just that one server (faster).<br />
  				Unfortunately, the list of players is often incomplete. This is not a 
  				bug on my side but one in the UT server. Sometimes trying for a few times can get you a 
  				larger list, but often not :(</p>
  
  			<table summary="Infiltration servers">
  				<caption>Infiltration servers</caption>
  				<thead>
  					<tr>
  						<th scope="col">Server</th>
  						<th scope="col">Map</th>
  						<th scope="col">GameType</th>
  						<th scope="col">Players</th>
  					</tr>
  				</thead>
  				<tfoot>
  					<tr>
  						<td colspan="4">List fetched on %s
  							| <a href="ut-serverbrowse-fork.php.txt">View source</a></td>
  					</tr>
  				</tfoot>
  				<tbody>
  					%s
  				</tbody>
  			</table>
  
  			<form method="post" action="%s">
  				<div>
  					<label for="server">Manually enter server to query. <em>address:port</em>: </label>
  					<input type="text" id="server" name="server" value="%s"/>
  					<input type="submit" value="Query" />
  				</div>
  			</form>', $charset, date('r'), $tbl, $_SERVER['PHP_SELF'], $_GET['server']);
  
  	if (isset($_GET['server']))
  		printf('<p><a href="%s">Back to main</a></p>', $_SERVER['PHP_SELF']);
  
  	print '</body></html>';
  }
  
  # We do not use a masterserver but maintain the list here manually; This is a 
  # feature and not a bug because the masterserve for the mod I play 
  # (Infiltration) is not particuarly reliable.
  $servers = array(
  	array('cerberon.net', 17778), # coop1
  	array('cerberon.net', 27778), # coop2
  	array('90.184.183.249', 7778), # wargamez
  	array('xplod.de', '7778'), # xplod
  	array('xplod.de', '8889'), # xplod2
  	array('84.255.245.128', '7778') # planetweed
  );
  
  if (isset($_POST['server']))
  	$_GET['server'] = $_POST['server'];
  
  if (isset($_GET['server']))
  {
  	$a = explode(':', $_GET['server']);
  	$servers = array(array($a['0'], $a['1']));
  }
  
  $mypid = getmypid();
  $procs = array();
  $tmp = tempnam(sys_get_temp_dir(), 'SERVERBROWSE_');
  $fp = fopen($tmp, 'w+');
  foreach ($servers as $s)
  {
  	$pid = pcntl_fork();
  
  	if ($pid == -1)
  	{
  		print 'Unable to fork. Exiting';
  		exit(1);
  	}
  
  	if ($pid == 0)
  	{
  		$data = Server($s);
  		fwrite($fp, $data) . "\n";
  		posix_kill(getmypid(), 9);
  	}
  	else
  		array_push($procs, $pid);
  }
  
  # Wait for all childs to finish
  while (count($procs) > 0) 
  {
  	$pid = pcntl_waitpid(-1, $status, WNOHANG);
  
  	foreach($procs as $k => $p)
  	{
  		if($pid == $p)
  			unset($procs[$k]);
  	}
  	usleep(250);
  }
  
  fclose($fp);
  
  $fp = fopen($tmp, 'r');
  $tbl = '';
  while ($buf = fread($fp, 8192))
  	$tbl .= $buf;
  fclose($fp);
  unlink($tmp);
  
  html($tbl);
  exit(0);
  
  ?>
The code without fork
This is the old version without forking, it”s slower but since pcntl_fork() is not always available it may still be of some interest.

Code:
  <?php
  #
  # Martin Tournoij <martin@arp242.net>
  # Free for any use. There are no restrictions.
  #
  
  function Query($server, $cmd)
  {
  	# http://wiki.beyondunreal.com/Legacy:UT_Server_Query
  
  	$address = gethostbyname($server['0']);
  	
  	if (!$s = fsockopen('udp://' . $server['0'], $server['1'], $errno, $errstr, 2))
  		return False;
  
  	stream_set_timeout($s, 2);
  	fputs($s, "\\$cmd\\");
  	$info = fread($s, 8192);
  	fclose($s);
  	$info2 = explode('\\', "\\$info\\");
  
  	foreach ($info2 as $k => $v)
  	{
  		if (empty($v) || $v == '' || $v == ' ')
  			continue;
  		if (is_int($k/2))
  			$pv = $v;
  		else
  			$prettyinfo["$pv"] = htmlentities($v);
  
  	}
  	$prettyinfo['str'] = $info;
  
  	return $prettyinfo;
  }
  
  # We do not use a masterserver but maintain the list here manually; This is a 
  # feature and not a bug because the masterserve for the mod I play 
  # (Infiltration) is not particuarly reliable.
  $servers = array(
  	array('cerberon.net', 17778), # coop1
  	array('cerberon.net', 27778), # coop2
  	array('90.184.183.249', 7778), # wargamez
  	array('xplod.de', '7778'), # xplod
  	array('xplod.de', '8889'), # xplod2
  	array('84.255.245.128', '7778') # planetweed
  );
  
  if (isset($_POST['server']))
  	$_GET['server'] = $_POST['server'];
  
  if (isset($_GET['server']))
  {
  	$a = explode(':', $_GET['server']);
  	$servers = array(array($a['0'], $a['1']));
  }
  
  $tbl = array();
  foreach ($servers as $s)
  {
  	$info = Query($s, 'status');
  
  	if (empty($info['str']))
  	{
  		# Just hide the entry, servers seem to be going up & down more often than 
  		# the space shuttle ...
  		#$tbl .= "<tr><td colspan=\"4\">Could not connect to {$s['0']} {$s['1']}</td></tr>";
  		continue;
  	}
  	
  	if (isset($info['numplayers']))
  	{
  		$numplayers = $info['numplayers'] . " Players:<br />\n";
  		$players = Query($s, 'players');
  
  		for ($i=0; $i<=$info['maxplayers']; $i++)
  		{
  			$k = "player_$i";
  			if ($players["$k"])
  				$numplayers .= $players["$k"] . "<br />\n";
  		}
  	}
  	else
  		$numplayers = 'No players';
  
  	# XXX Detect DTAS in a better way
  	if ($info['gametype'] == 'INFCoopUnrealGame')
  		$gametype = 'Coop';
  	elseif ($info['gametype'] == 'INFg_EASGame')
  		$gametype = 'EAS';
  	elseif (stristr($info['hostname'], 'dtas'))
  		$gametype = 'DTAS';
  	elseif ($info['gametype'] == 'InfilTeamGamePlus')
  		$gametype = 'TDM';
  	else
  		$gametype = $info['gametype'];
  
  	$tbl[] = sprintf('<tr>
  		<td><a href="ut-serverbrowse.php?server=%s:%s">%s</a><br />
  		<span class="small">%s:%s</span></td>
  		<td>%s</td>
  		<td>%s</td>
  		<td>%s</td>
  		</tr>',
  		$s['0'], $s['1'], $info['hostname'], $s['0'], $s['1'], $info['mapname'], $gametype, $numplayers);
  }
  
  $tbl = implode('', $tbl);
  if (stristr($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml'))
  	$charset = 'application/xhtml+xml; charset=utf-8';
  else
  	$charset = 'text/html; charset=utf-8';
  header("Content-Type: $charset");
  ?>
  <?php echo '<?xml version="1.0" encoding="UTF-8"?>' ?>
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
      "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
  	<head>
  		<meta http-equiv="Content-Type" content="<?php print $charset; ?>" /> 
  		<title>Infiltration Servers</title>
  		<style type="text/css">
  			table { border-collapse: collapse; margin: .25em; width: 95%; }
  			td, th, table, caption { border: 1px solid black; }
  			tfoot td { border: none; font-size: .8em; }
  			caption, th, tfoot { background-color: #ddd; }
  			caption { font-weight: bold; }
  			tr:hover { background-color: #eee; }
  			div { border: 1px solid black; margin: .25em; width: 94%; padding: .2em; margin-top: 2em; }
  			label { width: 6em; }
  			.small { font-size: .75em; margin-left: 2em; }
  			p { width: 40em; }
  		</style>
  	</head>
  	<body>
  		<p>Refresh your browser and the list will be refreshed. Click on a link to 
  			refresh just that one server (faster).<br />
  			Unfortunately, the list of players is often incomplete. This is not a 
  			bug on my side but one in the UT server. Sometimes trying for a few times can get you a 
  			larger list, but often not :(</p>
  
  		<table summary="Infiltration servers">
  			<caption>Infiltration servers</caption>
  			<thead>
  				<tr>
  					<th scope="col">Server</th>
  					<th scope="col">Map</th>
  					<th scope="col">GameType</th>
  					<th scope="col">Players</th>
  				</tr>
  			</thead>
  			<tfoot>
  				<tr>
  					<td colspan="4">List fetched on <?php print date('r'); ?>
  						| <a href="ut-serverbrowse.php.txt">View source</a></td>
  				</tr>
  			</tfoot>
  			<tbody>
  				<?php print $tbl; ?>
  			</tbody>
  		</table>
  
  		<form method="post" action="<?php print $_SERVER['PHP_SELF'] ?>">
  			<div>
  				<label for="server">Manually enter server to query. <em>address:port</em>: </label>
  				<input type="text" id="server" name="server" value="<?php print $_GET['server'] ?>"/>
  				<input type="submit" value="Query" />
  			</div>
  		</form>
  
  		<?php if (isset($_GET['server'])) { ?>
  			<p><a href="<?php print $_SERVER['PHP_SELF'] ?>">Back to main</a></p>
  		<?php } ?>
  
  	</body>
  </html>
__________________
UNIX was not designed to stop you from doing stupid things, because that would also stop you from doing clever things.
Reply With Quote
Reply

Tags
games, php, unreal tournament

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Manage Unreal Tournament cache files Carpetsmoker Guides 0 24th June 2010 10:11 PM
FreeBSD Capsicum USENIX Security 2010 paper now online J65nko News 0 2nd June 2010 08:42 PM
How secure is updating and installing online revzalot OpenBSD Security 1 4th September 2008 01:42 AM
Java online tutorial 18Googol2 Programming 5 28th August 2008 03:07 AM
divx online under freebsd ABRAXAS FreeBSD General 4 20th May 2008 03:34 PM


All times are GMT. The time now is 07:21 AM.


Powered by vBulletin® Version 3.8.4
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Content copyright © 2007-2010, the authors
Daemon image copyright ©1988, Marshall Kirk McKusick