Jump to content

Recommended Posts

Hello,

 

I'm trying to update my database with current statistics from my gameservers, but running the loop of socket connections to all of the servers on page load takes too long. For every server in the array it takes roughly 4 seconds to complete each one.. with a massive list of say 42 it takes awhile. However, I'm running from shared hosting and was wondering if that was the reason each server takes so long to query. I was thinking about running a cron job every 1 minute to update the information so it's still relativity new and current. Will the 1 minute cron job affect anything from the shared hosting? I'm using 1and1 web-hosting on a 1&1 Business Package.

 

Thanks

Link to comment
https://forums.phpfreaks.com/topic/228096-looping-socket-connections/
Share on other sites

yes, use a cron job. will it bother 1and1? ask them.

 

Okay.

 

Is it common for the socket to take 4 seconds in total for each connection? It's recommended on 1and1 that you only run a cron job every 5 minutes, because it could queue up the server a lot, but I want to run the script every minute or minute and a half so the database is semi-current... do you think that would be too much stress on the server?

 

Is it common for the socket to take 4 seconds in total for each connection? It's taking a really long time to refresh all of the content that I need.. there has to be a better and faster way. I've seen websites doing something similar and they have 40++ servers that they're pulling data from and they refresh their database every minute so it's only taking them a minute.

What do you mean by socket connections?  Are you actually connecting over with the socket library, or are you hitting the servers via CURL or SOAP or something?  And can you provide a little info of the exchange that's taking place and what you're doing with that response so I can suggest how to do it in parallel?

What do you mean by socket connections?  Are you actually connecting over with the socket library, or are you hitting the servers via CURL or SOAP or something?  And can you provide a little info of the exchange that's taking place and what you're doing with that response so I can suggest how to do it in parallel?

 

I'm trying to query my gameservers and get the map and how many players are in there. Here's the code I use to loop the game servers:

foreach($serverList as $server)
{

$r = new rcon($server['ip'], $server['port'], $server['password']);

if(!$r->isValid())
    $serverInformation[$server['ip'] . ':' . $server['port']]['error'] = 'Unable to connect to server (' . $server['ip'] . ':' . $server['port'] . ')';

if(!$r->Auth())
            $serverInformation[$server['ip'] . ':' . $server['port']]['error'] = 'Unable to authenticate! Wrong password?';

$status = $r->sendRconCommand("status");

$status = str_replace("\x0a", "", $status);

$information = getServerInformation($status); // regex the result and return important content

$serverInformation[$server['ip'] . ':' . $server['port']] = $information; // store important content in the array

}

 

Here's the rcon class too:

<?php
/*
CS:S Rcon PHP Class - code by 1FO|zyzko 01/12/2005
www.1formatik.com - www.1fogames.com
    --------------------------------------------------
*/
define("SERVERDATA_EXECCOMMAND", 02);
define("SERVERDATA_AUTH", 03);

class rcon {
    var $Password;
    var $Host;
    var $Port = 27015;
    var $_Sock = null;
    var $_Id = 0;
    var $valid = false;

    function __construct($Host,$Port,$Password) {
  	  return $this->init($Host,$Port,$Password);
    }

    function rcon ($Host,$Port,$Password) {
    	return $this->init($Host,$Port,$Password);
    }

    function init($Host,$Port,$Password) {
        $this->Password = $Password;
    	$this->Host = $Host;
    	$this->Port = $Port;
    	$this->_Sock = @fsockopen($this->Host,$this->Port, $errno, $errstr, 30);// or
    	    //die("Unable to open the port: $errstr ($errno)\n"+$this->Host+":"+$this->Port);
        if($this->_Sock) {
            $this->_Set_Timeout($this->_Sock,2,500);
            $this->valid = true;
        }
    }

    function isValid() {
        return $this->valid;
    }

    function Auth() {
    	$PackID = $this->_Write(SERVERDATA_AUTH, $this->Password);

    	$ret = $this->_PacketRead();
    	if ($ret[1]['ID'] == -1) {
            return 0;
    	} else {
            return 1;
      }
    }

    function _Set_Timeout(&$res,$s,$m=0) {
    	if (version_compare(phpversion(),'4.3.0','<')) {
            return socket_set_timeout($res,$s,$m);
    	}
    	return stream_set_timeout($res,$s,$m);
    }

    function _Write($cmd, $s1='', $s2='') {
    	$id = ++$this->_Id;
    	$data = pack("VV",$id,$cmd).$s1.chr(0).$s2.chr(0);
    	$data = pack("V",strlen($data)).$data;
    	fwrite($this->_Sock,$data,strlen($data));
    	return $id;
    }

    function _PacketRead() {
    	$retarray = array();
    	while ($size = @fread($this->_Sock,4)) {
  	    $size = unpack('V1Size',$size);
  	    if ($size["Size"] > 4096) {
    		  $packet = "\x00\x00\x00\x00\x00\x00\x00\x00".fread($this->_Sock,4096);
    	  } else {
    		  $packet = fread($this->_Sock,$size["Size"]);
    	  }
    	    array_push($retarray,unpack("V1ID/V1Reponse/a*S1/a*S2",$packet));
    	}
    	return $retarray;
    }

    function Read() {
    	$Packets = $this->_PacketRead();
    	foreach($Packets as $pack) {
    	  if (isset($ret[$pack['ID']])) {
            $ret[$pack['ID']]['S1'] .= $pack['S1'];
            $ret[$pack['ID']]['S2'] .= $pack['S1'];
    	  } else {
            $ret[$pack['ID']] = array(
                'Reponse' => $pack['Reponse'],
                'S1' => $pack['S1'],
                'S2' =>	$pack['S2'],
            );
      	  }
    	}
    	return $ret;
    }

    /**
     * Send an rcon command the server.  The command must be properly formatted before
     * sending to this method.  This means that variables that require quotes must have
     * quotes put around them.
     * Ex. $command = "kickid ". "\"".$steamId."\" ".$banReason
     */
    function sendRconCommand($command) {
      $this->_Write(SERVERDATA_EXECCOMMAND, $command, '');
      $ret = $this->Read();
      return $ret[$this->_Id]['S1'];
    }

}
    
?>

 

Don't get me wrong, because the code does run, but it's just really slow (4 seconds) for each loop in the foreach loop. I'm hoping there's a way to make this faster..

It'd be a bit of work to make the changes, and fsockopen() still blocks per connection, so you'll have some delay there, but then all the servers can be polled at once.  If you want to get really fancy, you can also just keep the script running and sockets open if your host and servers allow you to, which is how those bigger sites keep live data on hundreds of servers.

 

Basically what you'd have to do is start by changing the rcon class to static so that you can pool the connections outside of it, and then make the methods take a $socket parameter that would replace the references to $this->_socket;

 

In init(), after $this->_sock() is created, make it non blocking with stream_set_blocking(), and init() should return the socket object.

That class also has two sets of send/receive methods that you use, Auth() and sendRconCommand().  You would need to split that into 4 methods, one each for sending and one each for receiving.  I'll call it sendAuth()/receiveAuth() and sendRconCommand()/receiveRconCommand(). 

 

In your code then you would loop through all the servers calling rcon::init(), and create the an array of the object's socket connections with the server ip and port as the key.  I'll call this one $rconsSockets

 

You would loop through $rconsSockets and fire rcon::sendAuth() on each one, this will immediately do the sendAuth() command to all the servers. Use stream_select() and process those responses as they become available.

 

If you were going to keep the script and those sockets alive, this next part would be in a while(true) loop or something like that.

 

Repeat the process of sending the status command to all the servers, and then reading and processing the response as they become available.

 

The code to do this would look something like:

 


$numOfSockets = count($rconsSockets);

foreach($rconsSockets as $socket)
{
    rcon::sendRconCommand("status", $socket);
}

$n = 0;
$responses = array();
$write = array();
$except = array();

while ($n < $numOfSockets)
{
    $respondedStreams = stream_select($rconsSockets, $write, $except, 0);
    foreach($respondedStreams as $stream)
    {
        $key = array_search($stream, $rconsSockets, true);
        $response[$key] = rcon::receiveAuth($stream);
    }
    $n += count($respondedStreams);
}

 

What that code does is grabs the responses as they become available from each server and throws them into that $response array that you can either use later or do the getServerInformation() processing immediately.  How that's setup will block to the slowest server, but so does the current code and that does it sequentially.

 

A bit of work, and could probably be architected better than what I'm proposing and would need some better error handling, but this gives you an idea of how to do iwhat you're trying to do very quickly and efficiently.  If you end up doing it, I'm sure the rcon community would appreciate it being kicked back to them as well.

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.