2
\$\begingroup\$

I created a PHP script which will be running 24/7 as a TCP socket server and will send data from the DB back to clients. The code is working, and so far, I see no problems.

However, I can only test it with one client. The info I need is, what will happen when there will be 100+ clients? Can any of you see any weaknesses in the code? Can I test it somehow?

include('db_con.php');
include('db_functions.php');
error_reporting(E_ALL);

// Allow the script to hang around waiting for connections
set_time_limit(0);

// Turn on implicit output flushing so we see what we're getting as it comes in
ob_implicit_flush();

$address = '10.4.1.6';
$port = 4950;

$failed = 0;

try {
    // Create a socket (AF_INET = IPV4, SOCK_STREAM=TCP stream)
    $failed = 1;
    $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

    // Bind connection to IP/port
    $failed = 2;
    socket_bind($sock, $address, $port);

    $failed = 3;
    socket_listen($sock, SOMAXCONN);

    while (true) {
        // Accept connection
        $failed = 4;
        $client = socket_accept($sock);

        $failed = 6;
        $rec = socket_read($client, 1024);
        $id_d = substr($rec, 0, 1);
        $id = intval(substr($rec, 1, 1));

        // Connect to database and get results
        $failed = 7;

        $failed = 8;
        $data = "ok\r\n";

        if (strcmp($id_d, 'o') == 0) {
            $in_O = returnResultsForTCP_O($db, $id);
            foreach ($in_O as $r) {
                $data .= 'o'
                        . $r['ring_time'] . ' '
                        . ((int) $r['duration'] < 100 ? '0' : '')
                        . $r['duration'] . ' '
                        . $r['bell_mode'] . '\r\n';
            }
        } else {
            $in_D = returnResultsForTCP_D($db, $id);
            $in_W = returnResultsForTCP_W($db, $id);
            foreach ($in_D as $r) {
                $data .= 'd'
                        . $r['ring_time'] . ' '
                        . ((int) $r['duration'] < 100 ? '0' : '')
                        . $r['duration'] . ' '
                        . $r['bell_mode'] . '\r\n';
            }

            foreach ($in_W as $r) {
                $data .= 'w'
                        . $r['ring_time'] . ' '
                        . ((int) $r['duration'] < 100 ? '0' : '')
                        . $r['duration'] . ' '
                        . $r['day'] . ' '
                        . $r['bell_mode'] . '\r\n';
            }
        }

        $failed = 5;
        socket_write($client, $data);
        socket_close($client);
    }
} catch (Exception $e) {
    if ($failed < 6) {
        $causes = array('create', 'bind', 'listen', 'accept', 'write');
        echo 'socket_' . $causes[$failed - 1] . '() failed. Reason: '
                . socket_strerror(socket_last_error($sock)) . '\n';
    } else {
        echo 'Failed at ' . $failed . '\n' . $e . '\n';
    }

    socket_close($sock);
}

The $failed part is just for testing; I'll remake it later (change it so the logs will be written to file - logging).

\$\endgroup\$
2
\$\begingroup\$

Here are a few things:

The loop only handles one connection at a time, which could cause some issues for other clients connecting in the future. If returnResultsForTCP_O is slow and blocking, that could even prevent other clients from working.

Two possible solutions include:

Use http://reactphp.org/ to handle the connections

Or if you would still like to handle the connections manually, you can accept many connections at the same time by adding them all to an array and using socket_select to get arrays of all the sockets ready to read from or write to:

http://php.net/manual/en/function.socket-select.php

You would still need to make the database calls non-blocking. Again, reactphp has some database connection libraries available for it. Otherwise you will have to find another way to access the database in a non-blocking way. Although it isn't nonblocking, you can make a secondary php process you open with proc_open and pass the request into. Then just set stream_set_blocking on the proc_open pipe file handles, and read from it in your main loop.

\$\endgroup\$
  • \$\begingroup\$ Great answer, thank you, but I think I' probably overreacting with everything. As far as I know, PHP (or sockets in any other programming language) are put in queue (the maximum length is determined with SOMAXCONN in socket_listen), and there are no problems until this queue is full (if it is, the client will get a fatal error) or the server is taking too much time to respond. Since I will only have like 200 clients max, that will require data once a day (for the else part of the code) and every 5 minutes (if part of the while loop) there should be no problems with full queue. \$\endgroup\$ – Rok Dolinar Jun 27 '16 at 23:11
  • \$\begingroup\$ + SELECT statements (in returnResults functions) are very simple, like: SELECT e_id, ring_time, bell_mode, duration FROM data_d WHERE c_id = ? However, I will try to implement your suggestions, just to be sure. \$\endgroup\$ – Rok Dolinar Jun 27 '16 at 23:13
  • \$\begingroup\$ Yes, if your database request is very light as you mentioned, and the 200 clients are unlikely to make more than a connection or two at the same time, it should be fine. \$\endgroup\$ – Ryan Jun 27 '16 at 23:25
  • \$\begingroup\$ Hmm, but there is a possibility (theoretically of course) that 100+ clients try to connect at the same time. How big can the queue be? Should I change the SOMAXCONN to something like 200 (if it is not higher on linux already) and the only consequence will be higher response times (which do not matter since I do not care if client gets response after (lets say) 5 seconds).? EDIT: somaxconn default value is 128, I think it should be fine. \$\endgroup\$ – Rok Dolinar Jun 28 '16 at 17:04
  • \$\begingroup\$ I would suggest perhaps performing a test against your code with a simple PHP socket client. If your database function call is hard to test, have it sleep for 2-4 seconds to simulate a blocking database call. You could execute your test script in separate php processes 100 times simultaneously. \$\endgroup\$ – Ryan Jun 28 '16 at 20:22

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.