<?php
 
class Console{
 
    function log($d){
 
        $pid = getmypid();
 
        $color = "\033[1;".(30 + ($pid & 7)).";".(40 + (($pid >> 3) & 7)).((($pid >> 6) & 1)?';4':'')."m";
 
        printf("[%s]{$color}%-5s\033[0m: %s\n", date('m/d/Y h:i:s'), $pid, $d);
 
    }
 
}
 
class WebSocket{
 
    // How long in seconds to cleanup connections and garbage collect.
 
    const CLEANUP_TIMER = 10;
 
    
 
    const CHILD_PROCESS_RESPONSE_CLOSE = 'c';
 
    const CHILD_PROCESS_RESPONSE_RELAY_TO = 'i';
 
    public $link;
 
    public $lastCleanup = 0;
 
    public $children = array();
 
 
    function __construct($address,$port){
 
        global $child;
 
        Console::log("Creating WebSocket");
 
        ($this->link = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))    || die("socket_create() failed");
 
        Console::log("Configuring WebSocket");
 
        socket_set_option($this->link, SOL_SOCKET, SO_REUSEADDR, 1)        || die("socket_option() failed");
 
        Console::log("Binding WebSocket");
 
        socket_bind($this->link, $address, $port)                        || die("socket_bind() failed");
 
        Console::log("Listening on WebSocket");
 
        socket_listen($this->link)                                        || die("socket_listen() failed");
 
        Console::log("Setting WebSocket to non-blocking");
 
 
        Console::log("Server Started");
 
        Console::log("Listening on: $address port $port");
 
        Console::log("Master socket: $this->link");
 
 
        $lastCleanup = time();
 
        while(true){
 
            while(($child_id = pcntl_wait($status, WNOHANG)) > 0){ // Removes zombie children if they exist
 
                Console::log("Removed zombie: $child_id");
 
                if(isset($this->children[$child_id])){
 
                    socket_close($this->children[$child_id]);
 
                    unset($this->children[$child_id]);
 
                }
 
            }
 
            
 
            $connections = array_merge(array('master' => $this->link), $this->children);
 
            socket_select($connections, $write = null, $except = null, static::CLEANUP_TIMER);
 
            foreach($connections as $connection){
 
                if($connection === $this->link){
 
                    // Is a connection request from the web browser
 
                    $client = @socket_accept($this->link);
 
                    if(!$client){
 
                        Console::log("Web Client connection attempted but failed");
 
                        continue;
 
                    }else{
 
                        socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $ary);
 
                        $pid = pcntl_fork();
 
                        if($pid == -1){
 
                            Console::log("Could not fork process");
 
                        }elseif($pid){
 
                            // Parent
 
                            socket_close($ary[0]);
 
                            $this->children[$pid] = $ary[1];
 
                            $child = false;
 
                        }else{
 
                            // Child
 
                            socket_close($ary[1]);
 
                            global $parent;
 
                            $parent = $ary[0];
 
                            $child = $client;
 
                            return;
 
                        }
 
                        unset($ary);
 
                    }
 
                }else{
 
                    // Child communicating with parent
 
                    Console::log("Getting data from child");
 
                    if($l = socket_recv($connection, $len_data, 3, MSG_WAITALL)){
 
                        $len = (ord($len_data{0}) << 16) | (ord($len_data{1}) << 8) | (ord($len_data{2}));
 
                        Console::log("Got data of length: $l from child");
 
                        if(!$len)
 
                            continue;
 
                        elseif(socket_recv($connection, $data, $len, MSG_WAITALL) == $len){
 
                            Console::log("Got data from child!");
 
                            switch($data{0}){
 
                                case self::CHILD_PROCESS_RESPONSE_CLOSE:
 
                                    Console::log("WebSocket: Received notice from child about death");
 
                                    $this->closeChild(array_search($connection, $this->children, true));
 
                                    break;
 
                                case self::CHILD_PROCESS_RESPONSE_RELAY_TO:
 
                                    if(!$this->fork())
 
                                        break;
 
                                    $child = null;
 
                                    $pids = substr($data, 1, $len = strpos($data, ':'));
 
                                    $pids = explode(',', $pids);
 
                                    if(!$pids){
 
                                        Console::log("No Process IDS passed with packet, forgetting packet!");
 
                                        break;
 
                                    }
 
                                    foreach($pids as &$pid)
 
                                        $pid = (int) $pid;
 
                                    unset($pid);
 
                                    $data = substr($data, $len+1);
 
                                    $len = strlen($data);
 
                                    if($len > 0xFFFFFF){
 
                                        Console::log("Could not send packet too large!");
 
                                        exit;
 
                                    }
 
                                    for($i=0;$i<3;$i++)
 
                                        $data = chr(($len >> ($i * 8)) & 0xFF).$data;
 
                                    $len = strlen($data);
 
                                    foreach($pids as $pid)
 
                                        if(isset($this->children[$pid])){
 
                                            $i=0;
 
                                            do{
 
                                                if(($d = @socket_send($this->children[$pid], $da = substr($data, $i), strlen($da), 0)) === false){
 
                                                    Console::log("WebSocket: Failed to send data to: {$this->children[$pid]}");
 
                                                    $this->closeChild($pid);
 
                                                    break;
 
                                                }
 
                                                if(!$d)
 
                                                    usleep(25);
 
                                                $i += $d;
 
                                            }while($len > $i);
 
                                        }
 
                                    unset($pid, $len, $data, $i, $pids, $d, $len_data, $l, $da);
 
                                    global $forked;
 
                                    $forked = true;
 
                                    $child = true;
 
                                    exit;
 
                                default:
 
                                    if(!$this->fork())
 
                                        break;
 
                                    $child = null;
 
                                    Console::log("Relaying to all children");
 
                                    $data = $len_data . $data;
 
                                    $len = strlen($data);
 
                                    foreach($this->children as $k => $c){
 
                                        $i=0;
 
                                        do{
 
                                            if(($d = @socket_send($c, $da = substr($data, $i), strlen($da), 0)) === false){
 
                                                Console::log("WebSocket: Failed to send data to: $c");
 
                                                $this->closeChild($k);
 
                                                break;
 
                                            }
 
                                            if(!$d)
 
                                                usleep(25);
 
                                            $i += $d;
 
                                        }while($len > $i);
 
                                    }
 
                                    unset($k, $c, $d, $i, $len, $len_data, $data, $l, $da);
 
                                    global $forked, $child;
 
                                    $forked = true;
 
                                    $child = true;
 
                                    exit;
 
                            }
 
                        }else{
 
                            Console::log("WebSocket: Not enough data received from child process");
 
                            $this->closeChild(array_search($connection, $this->children, true));
 
                        }
 
                    }else
 
                        $this->closeChild(array_search($connection, $this->children, true));
 
                }
 
            }
 
            $data = '';
 
        }
 
    }
 
    public function fork(){
 
        $id = pcntl_fork();
 
        if($id == -1){
 
            return false;
 
        }elseif($id)
 
            // parent
 
            return false;
 
        else
 
            // child
 
            set_time_limit(30);
 
            return true;
 
    }
 
    public function closeChild($process_id){
 
        if(!$process_id)
 
            return;
 
        $connection = $this->children[$process_id];
 
        @socket_send($connection, "\x00\x00\x01c", 4, 0);
 
        @socket_close($connection);
 
        unset($this->children[$process_id]);
 
    }
 
    public function __destruct(){
 
        global $child;
 
        if($child)
 
            return;
 
        foreach($this->children as $pid => $child)
 
            $this->closeChild($pid);
 
        @socket_close($this->link);
 
    }
 
}
 
 |