needs help to debug a socketServer in php

321 views Asked by At

A few months ago I needed to create a LIVE table showing actions of visitors in a website. So I started learning socket programming in php by googling around and using some codes written by others. The result was creating these two files which helped me achieve what I needed.

But for some unknown reason sometimes socket server stops accepting new connections although I can see it still running in the processes of my server. this happens only once or twice a month and I had to kill the process and start it again. The website has about 1500 visitors per day and each one stays for about 2-10 minutes online and fill some forms and we will see their behavior on a live table.

I have two different set of clients connecting to this server. first one is Moderators who can see the live table and the second one is my web application sending packets to update the information in the table whenever visitors make some changes or do some stuff.

I'm using screen command in ssh to run the process.

So what I need is to find a way to debug it and find the problem. Or maybe someone can find the problem by reading my codes here.

socket.php :

<?php
class socket {
    function send($socket, $message) {
        $messageLength = strlen($message);
        return @socket_write($socket,$message,$messageLength);
    }

    function broadcast($clients, $message) {
        $messageLength = strlen($message);
        foreach($clients as $socket) {
            @socket_write($socket, $message, $messageLength);
        }
        return true;
    }

    function unseal($socketData) {
        $length = @ord($socketData[1]) & 127;
        if($length == 126) {
            $masks = substr($socketData, 4, 4);
            $data = substr($socketData, 8);
        }
        elseif($length == 127) {
            $masks = substr($socketData, 10, 4);
            $data = substr($socketData, 14);
        }
        else {
            $masks = substr($socketData, 2, 4);
            $data = substr($socketData, 6);
        }
        $socketData = "";
        for ($i = 0; $i < strlen($data); ++$i) {
            $socketData .= $data[$i] ^ $masks[$i%4];
        }
        return $socketData;
    }

    function seal($socketData) {
        if(is_array($socketData)) {
            $socketData = json_encode($socketData);
        }
        $b1 = 0x80 | (0x1 & 0x0f);
        $length = strlen($socketData);

        if($length <= 125)
            $header = pack('CC', $b1, $length);
        elseif($length > 125 && $length < 65536)
            $header = pack('CCn', $b1, 126, $length);
        elseif($length >= 65536)
            $header = pack('CCNN', $b1, 127, $length);
        return $header.$socketData;
    }

    function doHandshake($received_header,$client_socket_resource, $host_name, $port) {
        $headers = $this->headersToArray($received_header);

        if(isset($headers['Sec-WebSocket-Key'])) {
            $secKey = $headers['Sec-WebSocket-Key'];
            $secAccept = base64_encode(pack('H*', sha1($secKey . 'XXXX-XXXX')));
            $buffer = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
                "Upgrade: websocket\r\n" .
                "Connection: Upgrade\r\n" .
                "WebSocket-Origin: $host_name\r\n" .
                "WebSocket-Location: ws://$host_name:$port/\r\n" .
                "Sec-WebSocket-Accept:$secAccept\r\n\r\n";
            socket_write($client_socket_resource, $buffer, strlen($buffer));
            return $headers;
        } else {
            return false;
        }
    }

    function headersToArray($received_header) {
        $headers = array();
        $lines = preg_split("/\r\n/", $received_header);
        foreach($lines as $line)
        {
            $line = chop($line);
            if(preg_match('/\A(\S+): (.*)\z/', $line, $matches))
            {
                $headers[$matches[1]] = $matches[2];
            }
        }
        return $headers;
    }

    function getCookiesFromHeader($headersArray)
    {
        $c = @$headersArray['Cookie'];
        $c = str_replace(' ','',$c);
        $cookies = explode(';',$c);
        $res = [];
        foreach($cookies as $cookie) {
            $temp = explode('=',$cookie);
            if(isset($temp[0],$temp[1])) {
                $res[$temp[0]] = $temp[1];
            }
        }
        return $res;
    }
}

and socket-server.php :

<?php
// don't timeout!
set_time_limit(0);

require_once('include/config.php');
require_once('core/socket.php');
require_once('core/db.php');

error_reporting(E_ALL);
ini_set('display_errors',1);

// create socket
$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create socket\n");
if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) {
    echo socket_strerror(socket_last_error($socket));
    exit;
}
// bind socket to port
$result = socket_bind($socket, SOCKET_HOST, SOCKET_PORT) or die("Could not bind to socket\n");
// start listening for connections
$result = socket_listen($socket, 3) or die("Could not set up socket listener\n");


$clients = array($socket);
$clientsToWrite = array();
$wsh = new socket();
$i = 0;
while (true) {
    $read = $clients;
    $msgToAll = '';
    // get a list of all the clients that have data to be read from
    // if there are no clients with data, go to next iteration
    $write = $except = NULL;
    if (socket_select($read, $write, $except, NULL) < 1)
        continue;

    if (in_array($socket, $read)) {
        //echo "socket is in array\n";
        $newSocket = socket_accept($socket);

        $header = @socket_read($newSocket, 1024);
        if(substr($header,0,6) == 'update') { // connection from web application
            $header = str_replace('update','',$header);
            $update = json_decode($header,true);
            $msgToAll = $update;
            //echo "New update received\n";
            socket_write($newSocket,'OK');
            socket_close($newSocket);
        } elseif($header == '3X!T') { // exit command
            socket_write($newSocket,'OK');
            socket_close($newSocket);
            socket_close($socket);
            die('');
        } else {
            // New connection from moderator section
            // Lets do handshake";
            if($headers_arr = $wsh->doHandShake($header, $newSocket, SOCKET_HOST, SOCKET_PORT)) {
                if ($data = $wsh->unseal(@socket_read($newSocket, 1024))) {
                    if ($session = checkSessData($data)) {
                        $clientsToWrite[] = [
                            'session' => $session,
                            'socket' => $newSocket,
                        ];
                        $clients[] = $newSocket;
                        // send the client a welcome message
                        $msg = ['type' => 'system', 'do' => 'connected'];
                        $msg = $wsh->seal($msg);
                        if ($bytes = socket_write($newSocket, $msg)) {
                            //echo "sent {$bytes} bytes to client\n";
                        } else {
                            $err = socket_strerror(socket_last_error());
                            echo "err {$err}\n";
                        }
                        socket_getpeername($newSocket, $ip);
                        //echo "New client connected: {$ip}\n";
                    } else {
                        $msg = ['type' => 'system', 'do' => 'unauthorized access'];
                        $msg = $wsh->seal($msg);
                        socket_write($newSocket, $msg);
                        socket_close($newSocket);
                    }
                } else {
                    socket_close($newSocket);
                }
            } else {
                socket_close($newSocket);
            }
        }

        // remove the listening socket from the clients-with-data array
        $key = array_search($socket, $read);
        unset($read[$key]);

    }

    if(!empty($msgToAll)) {
        $method = $msgToAll['method'];
        $user_id = $msgToAll['user_id'];
        unset($msgToAll['method'],$msgToAll['user_id']);
        $data = $wsh->seal($msgToAll);
        foreach ($clientsToWrite as $cid => $ctw) {
            if($ctw['session']['method'] == $method) {
                if ($ctw['session']['section'] == 'admin' || $ctw['session']['user_id'] == $user_id) {
                    // write the message to the client -- add a newline character to the end of the message
                    if(@socket_write($ctw['socket'], $data) === false) {
                        @socket_close($ctw['socket']);
                        unset($clientsToWrite[$cid]);
                        $key = array_search($ctw['socket'],$clients);
                        unset($clients[$key]);
                    }
                }
            }

        }
    }

    // loop through all the clients that have data to read from
    foreach ($read as $k => $read_sock) {
        //echo "loop through read {$k}\n";
        // read until newline or 1024 bytes
        // socket_read while show errors when the client is disconnected, so silence the error messages
        $data = socket_read($read_sock, 1024);
        $res = searchClients($read_sock, $clientsToWrite);

        // check if the client is disconnected
        if ($data === false) {
            // remove client for $clients array
            if($res) {
                unset($clientsToWrite[$res]);
            }
            //echo "client disconnected.\n";
            // continue to the next client to read from, if any
            continue;
        }

        if(empty($data))
            continue;
        $data = $wsh->unseal($data);
        //echo 'new data received : '.$data."\n";
        $data = json_decode($data,true);
        $message = '';
        DB::connect();
        switch($data['action']) {
            case 'addToSP' :
                // do some stuff in db
                $message = 'stuff done successfully!';
                break;
            case 'deleteFromSP' :
                // do some stuff in db
                $message = 'stuff done successfully!';
                break;
        }
        DB::disconnect();
        $message = [
            'type' => 'alert',
            'message' => $message,
        ];
        @socket_write($read_sock,$wsh->seal($message));

    } // end of reading foreach
}

// close the listening socket
socket_close($socket);

function checkSessData($data) {
    $data = json_decode($data,true);
    if(isset($data['method'],$data['user_id'],$data['hash'])) {
        $file_path = 'pathToLiveSessionsCache/'.$data['user_id'].'-'.$data['hash'].'.txt';
        if(file_exists($file_path)) {
            $content = file_get_contents($file_path);
            $content = explode(',',$content);
            if($content[0] == $data['section'].'_'.$data['method']) {
                if($content[1]+300 >= time()) { // max 5 min after starting page
                    unlink($file_path);
                    return $data;
                }
            }
            unlink($file_path);
        }
    }
    return false;
}

function searchClients($socket, $list)
{
    foreach($list as $k => $item) {
        if($item['socket'] == $socket) {
            return $k;
        }
    }
    return false;
}

This function will be used by my web application to send packets to socket server :

function sendDataToSocketServer($data,$type='update')
{
    $message = $type;
    if(!empty($data)) {
        $message .= json_encode($data);
    }
    $socket = socket_create(AF_INET, SOCK_STREAM, 0);
    socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => 1, 'usec' => 0));
    socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 1, 'usec' => 0));
    $result = socket_connect($socket, SOCKET_HOST, SOCKET_PORT);
    socket_write($socket, $message, strlen($message));
    $result = socket_read($socket, 1024);
    socket_close($socket);
}

This is also the js codes I'm using for moderators to connect to socket server :

var ws;
    function connect() {
        ws = new WebSocket('ws://myDomain.com:12345');
        ws.onopen = function () {
            ws.send('<?=$liveSessData;?>');
        };

        ws.onmessage = function (evt) {
            var msg = JSON.parse(evt.data);
            switch(msg.type) {
                case 'system':
                    systemNotif(msg);
                    break;
                case 'new':
                    newData(msg);
                    break;
                case 'change':
                    updateData(msg);
                    break;
            }
        };

        ws.onclose = function () {
            disconnected();
        };
    }
0

There are 0 answers