2
\$\begingroup\$

This Socket Server should handle about 5000 lines of log file entries per second from at least 15 machines from same network. Any tips to further optimize this script or are there any big mistakes?

class Server {

    private $callback;
    private $clients;
    private $socket;

    public function __construct($ip, $port) {
        $this->hooks = array();
        $this->clients = array();

        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
        socket_bind($this->socket, $ip, $port);
        socket_listen($this->socket);
    }

    public function setCallback($function) {
        $this->callback = $function;
    }

    public function loop_once() {
        $read = array( $this->socket );
        $write = NULL;
        $except = NULL;

        foreach ($this->clients as $index => &$client) {
            $read[] = &$client->socket;
        }

        if (socket_select($read, $write, $except, 1) < 1) {
            return true;
        }

        if (in_array($this->socket, $read)) {
            $this->clients[] = new Client($this->socket, 0);
        }

        foreach ($this->clients as $index => &$client) {
            if (in_array($client->socket, $read)) {
                $input = socket_read($client->socket, 1024, PHP_NORMAL_READ);
                if ($input === false) {
                    $client->destroy();
                    unset($this->clients[$index]);
                } else {
                    if (isset($this->callback)) {
                        call_user_func($this->callback, $input);
                    }
                }
            }
        }
        $this->clients = array_values($this->clients);
        return true;
    }
}


class Client {

    public $socket;

    public function __construct(&$socket, $i) {
        $this->socket = socket_accept($socket);
    }

    public function destroy() {
        socket_close($this->socket);
    }
}
\$\endgroup\$
  • \$\begingroup\$ * removed @-operator from socket_select and socket_read \$\endgroup\$ – Tobias Herkula Feb 23 '12 at 11:19
  • \$\begingroup\$ * added socket_set_option for reuse of address \$\endgroup\$ – Tobias Herkula Feb 23 '12 at 11:20
3
\$\begingroup\$

Try dropping the reference usage. Objects are copied by reference already:

<?php
$a = (object)array('one' => 1, 'two' => 2);
$b = $a;
$b->three = 3;

print_r($a);
print_r($b);
?>

Output:

stdClass Object
(
    [one] => 1
    [two] => 2
    [three] => 3
)
stdClass Object
(
    [one] => 1
    [two] => 2
    [three] => 3
)

However, arrays are copied by value (using copy-on-write):

<?php
$a = array('one' => 1, 'two' => 2);
$b = $a;
$b['three'] = 3;

print_r($a);
print_r($b);
?>

Output:

Array
(
    [one] => 1
    [two] => 2
)
Array
(
    [one] => 1
    [two] => 2
    [three] => 3
)

Since Client is an object type, you should be able to replace:

foreach ($this->clients as $index => &$client)

with:

foreach ($this->clients as $index => $client)

Actually, "objects are copied by reference" isn't entirely true. An "object" in PHP is just a handle referring to a shared entity, making it more like a reference in Java or a pointer in C. This is not true for arrays, where modification is confined to the variable upon which it happens.

References in PHP are one level above the notion of object handle. When you modify a reference, you are changing what the referent variable names. For example:

$obj = (object)array('one' => 1, 'two' => 2);
$a = $obj;
$a->three = 'three';

Now $obj and $a point to the same object, but they are distinct variables. Now let's do something funky with references:

$b =& $a;
$b = null;

This actually sets both $a and $b to null, but leaves $obj intact.

Confused? Good.

For more information about references in PHP, see:

\$\endgroup\$
  • \$\begingroup\$ $this->clients is an array and only the content are objects, but I see your point. \$\endgroup\$ – Tobias Herkula May 2 '12 at 12:02
1
\$\begingroup\$

Why are you suppressing socket_select errors - I'd suggest at least catching any errors.

\$\endgroup\$
  • \$\begingroup\$ Had problems with PHP Warning: socket_select(): unable to select [4]: Interrupted system call in because of the older use of declare(ticks = 1); changed that now to a call to pcntl_signal_dispatch(); in each loop to get all signaling handled properly. \$\endgroup\$ – Tobias Herkula Feb 23 '12 at 11:13
  • \$\begingroup\$ Ah I see, makes sense \$\endgroup\$ – BenOfTheNorth Feb 23 '12 at 11:21
0
\$\begingroup\$

The code looks good (a few comments would have helped me understand it quicker though).

I have only very minor things to comment on:

  • $index is not used in the first foreach. I believe this has a small impact on performance.
  • else { if () {} } is equivalent to elseif { }
  • Client constructor does not use $i
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.