Location: PHPKode > scripts > DutchPIPE - Avatar worlds on websites > dutchpipe-avatar-worlds-on-websites/lib/dpserver.php
<?php
/**
 * Provides 'DpServer' class to answer normal and AJAX requests from web clients
 *
 * DutchPIPE version 0.4; PHP version 5
 *
 * LICENSE: This source file is subject to version 1.0 of the DutchPIPE license.
 * If you did not receive a copy of the DutchPIPE license, you can obtain one at
 * http://dutchpipe.org/license/1_0.txt or by sending a note to
 * hide@address.com, in which case you will be mailed a copy immediately.
 *
 * @package    DutchPIPE
 * @subpackage lib
 * @author     Lennert Stock <hide@address.com>
 * @copyright  2006, 2007 Lennert Stock
 * @license    http://dutchpipe.org/license/1_0.txt  DutchPIPE License
 * @version    Subversion: $Id: dpserver.php 278 2007-08-19 22:52:25Z ls $
 * @link       http://dutchpipe.org/manual/package/DutchPIPE
 * @see        dpclient.php, dpuniverse.php
 */

/* Shows all possible errors */
error_reporting(E_ALL | E_STRICT);

/**
 * A DutchPIPE server to answer normal and AJAX requests from web clients
 *
 * The DutchPIPE server which can be started and then keeps running while
 * accepting socket connections. Waits for and accepts socket requests from
 * users connected through dpclient.php, and returns output to dpclient.php,
 * which returns it to the end user. If there are 10 users on the site, it will
 * loop through the main code for each user at a time.
 *
 * To answer each request, the server passes the request on to the persistent
 * DbUniverse object, where the real processing takes place.
 *
 * @package    DutchPIPE
 * @subpackage lib
 * @author     Lennert Stock <hide@address.com>
 * @copyright  2006, 2007 Lennert Stock
 * @license    http://dutchpipe.org/license/1_0.txt  DutchPIPE License
 * @version    Release: 0.2.1
 * @link       http://dutchpipe.org/manual/package/DutchPIPE
 * @see        dpclient.php, dpuniverse.php
 */
final class DpServer
{
    /**
     * @var         resource  The socket the server has opened to a client
     * @access      private
     */
     private $mMsgsock;

    /**
     * @var         resource  Used to show server status info on the cmd line
     * @access      private
     */
    private $mShowedStatusHeader = 0;

    /**
     * Used to show server status info based on getrusage, start "user" time of
     * script
     *
     * @var         resource  Start 'user' time of script
     * @access      private
     */
    private $mUtimeBefore;

    /**
     * Used to show server status info based on getrusage, start "system" time
     * of script
     *
     * @var         resource  Start "system" time of script
     * @access      private
     */
    private $mStimeBefore;

    /**
     * Sets up the server at object creation time based on your settings
     *
     * @param       string    $iniFile    Path to a dpserver-ini.php like file
     */
    function __construct($iniFile = 'dpserver-ini.php')
    {
        /* Used by showStatus() further below */
        if (function_exists('getrusage')) {
            $rusage = getrusage();
            $this->mUtimeBefore = (int)$rusage["ru_utime.tv_sec"] * 1e6 +
                (int)$rusage["ru_utime.tv_usec"];
            $this->mStimeBefore = (int)$rusage["ru_stime.tv_sec"] * 1e6 +
                (int)$rusage["ru_stime.tv_usec"];
        }

        /* Get the server settings */
        require_once($iniFile);

        require_once(DPSERVER_LIB_PATH . 'dpmbstring_'
            . (!DPSERVER_ENABLE_MBSTRING || !function_exists('mb_strlen')
            ? 'disabled' : 'enabled') . '.php');

        error_reporting(DPSERVER_ERROR_REPORTING);

        ini_set('memory_limit', DPSERVER_MEMORY_LIMIT);

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

        /* See what we're getting as it comes in */
        ob_implicit_flush();

        date_default_timezone_set(DPSERVER_TIMEZONE);
    }

    /**
     * Starts the DutchPIPE server, using a specific 'universe' object
     *
     * Also see: http://www.php.net/sockets
     *
     * @param       object    &$universe   An instance of DpUniverse
     */
    function runDpServer(&$universe)
    {
        /* Check if the server is already running */
        if (file_exists(DPSERVER_SOCKET_PATH)) {
            /* :KLUDGE: should be improved */
            unlink(DPSERVER_SOCKET_PATH);
            //die(dp_text("Cannot start server: server already running\n"));
        }

        if (DPSERVER_SOCKET_TYPE === AF_UNIX) {
            /* Initialize the server, first create a socket */
            if (FALSE === ($socket = socket_create(AF_UNIX, SOCK_STREAM, 0))) {
                die(sprintf(dp_text(
                    "socket_create(): unable to create socket [%u]: %s\n"),
                    socket_last_error(), socket_strerror(socket_last_error())));
            }

            /* Bind the socket to a file, for example /tmp/dutchpipesock */
            if (FALSE === socket_bind($socket, DPSERVER_SOCKET_PATH)) {
                die(sprintf(dp_text(
                    "socket_bind() unable to bind socket [%u]: %s\n"),
                    socket_last_error(), socket_strerror(socket_last_error())));
            }
            if (FALSE === chmod(DPSERVER_SOCKET_PATH, 0777)) {
                die(sprintf(dp_text(
                    "Cannot start server: reason: chmod on %s failed\n"),
                    DPSERVER_SOCKET_PATH));
            }
        } elseif (DPSERVER_SOCKET_TYPE === AF_INET) {
            /* Initialize the server, first create a socket */
            if (FALSE === ($socket = socket_create(AF_INET, SOCK_STREAM,
                    SOL_TCP))) {
                die(sprintf(dp_text(
                    "socket_create(): unable to create socket [%u]: %s\n"),
                    socket_last_error(), socket_strerror(socket_last_error())));
            }

            /* Reuse the port in case it was not properly closed */
            socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);

            /* Bind the socket to a file, for example /tmp/dutchpipesock */
            if (FALSE === socket_bind($socket, DPSERVER_SOCKET_ADDRESS,
                    DPSERVER_SOCKET_PORT)) {
                die(sprintf(dp_text(
                    "socket_bind() unable to bind socket [%u]: %s\n"),
                    socket_last_error(), socket_strerror(socket_last_error())));
            }
        } else {
            die(dp_text("Invalid socket protocol.\n"));
        }
        /* Start listening to the socket which dpclient.php talks to */
        if (FALSE === socket_listen($socket, DPSERVER_MAX_SOCKET_BACKLOG)) {
            die(sprintf(dp_text("socket_listen(): failure [%u]: %s\n"),
                socket_last_error(), socket_strerror(socket_last_error())));
        }

        /* Accept connections and loop for each request */
        do {
            if (FALSE === ($this->mMsgsock = socket_accept($socket))) {
                echo sprintf(dp_text("socket_accept(): failure [%u]: %s\n"),
                    socket_last_error(), socket_strerror(socket_last_error()));
                break;
            }

            /*
             * We got a connection with a user client through dpclient.php,
             * dpclient.php should send some serialized arrays with variables.
             * These are the PHP global $_SERVER, $_COOKIE, ... arrays.
             * All client info, including input and AJAX parameters, are send
             * through these arrays.
             */
            $allbuf = '';
            do {
                if (DPSERVER_SOCKET_TYPE === AF_UNIX) {
                    /* Read in what dpclient.php is telling us in chunks */
                    if (FALSE === ($buf = socket_read($this->mMsgsock,
                            DPSERVER_SERVER_CHUNK, PHP_NORMAL_READ))) {
                        echo sprintf(dp_text(
                            "socket_read(): failure [%u]: %s\n"),
                            socket_last_error(),
                            socket_strerror(socket_last_error()));
                        break 2;
                    }
                    if (!dp_strlen($buf = trim($buf))) {
                        continue;
                    }
                    /* This can be used to shutdown the server */
                    if ($buf == 'shutdown') {
                        socket_close($this->mMsgsock);
                        break 2;
                    }

                    /*
                     * Read the 2KB blocks until dpclient.php sends us a 'quit'
                     */
                    if ($buf <> 'quit') {
                        $allbuf .= $buf;
                        continue;
                    }
                } else {
                    /* Read in what dpclient.php is telling us in chunks */
                    if (FALSE === ($buf = socket_read($this->mMsgsock,
                            DPSERVER_SERVER_CHUNK))) {
                        echo sprintf(dp_text(
                            "socket_read(): failure [%u]: %s\n"),
                            socket_last_error(),
                            socket_strerror(socket_last_error()));
                        break 2;
                    }
                    if (!dp_strlen($buf = trim($buf))) {
                        continue;
                    }
                    $allbuf .= $buf;
                    /* This can be used to shutdown the server */
                    if (dp_strlen($allbuf) >= 7 &&
                            dp_substr($allbuf, -7) == 'shutdown') {
                        socket_close($this->mMsgsock);
                        break 2;
                    }
                    /*
                     * Read the 2KB blocks until dpclient.php sends us a 'quit'
                     */
                    if (dp_strlen($allbuf) < 4
                            || dp_substr($allbuf, -4) != 'quit') {
                        continue;
                    }
                    //$allbuf = dp_substr($allbuf, 0, dp_strlen($allbuf - 4));
                }

                /* Check for invalid input from dpclient.php */
                if (dp_strlen($allbuf) <= 6
                        || FALSE === ($pos1 = dp_strpos($allbuf, "<vars>"))
                        || FALSE === ($pos2 = dp_strpos($allbuf, "</vars>"))
                        || $pos2 <= $pos1 + 7) {
                    echo 'allbuf: ' . $allbuf . "\n";
                    break;
                }

                /*
                 * Cut out and unserialize the three global PHP vars
                 * dpclient.php passed on
                 */
                $vars = dp_substr($allbuf, 0, $pos2);
                $vars = dp_substr($vars, $pos1 + 6);
                if (TRUE === DPSERVER_BASE64_CLIENT2SERVER) {
                    $vars = base64_decode($vars);
                }
                $tmp = unserialize($vars);

                if (FALSE === $tmp) {
                    //$handle = fopen('/tmp/dpserver.log', 'a');
                    //fwrite($handle, sprintf(dp_text("No unserialize: %s\n"),
                    //$vars));
                    //fclose($handle);
                    echo sprintf(dp_text("No unserialize: %s\n"), $vars);
                    $__SERVER = $__SESSION = $__COOKIE = $__GET = $__POST =
                        $__FILES = array();
                }

                list($__SERVER, $__SESSION, $__COOKIE, $__GET, $__POST,
                    $__FILES) = $tmp;
                /*
                 * Pass on the request to the universe object. The universe
                 * object can response by calling tellCurrentDpUserRequest()
                 * below
                 */
                $universe->handleCurrentDpUserRequest($this, $__SERVER,
                    $__SESSION, $__COOKIE, $__GET, $__POST, $__FILES);

                /*
                 * :KLUDGE: Shows server status once in a while. The ticks
                 * system or external cron triggered calls should be used in the
                 * future
                 */
                if (1 === mt_rand(1, 2 + $universe->getNrOfUsers())) {
                    $this->_showStatus($universe);
                }
                break;
            } while (true);
            socket_close($this->mMsgsock);
        } while (true);

        socket_close($sock);
    }

    /**
     * Closes the socket when the server is destroyed.
     */
    function __destruct()
    {
        if ($this->mMsgsock) {
            socket_close($this->mMsgsock);
        }
    }

    /**
     * Tells a string to the current user request through dpclient.php
     *
     * Called from 'the universe' to talk back to the client, messages are
     * typically XML with something like '<message>Lennert says: hi</message>'.
     *
     * @param       string    $talkback   String to send to current user client
     */
    function tellCurrentDpUserRequest($talkback)
    {
        if (0 === dp_strlen($talkback)) {
            return;
        }

        if (TRUE === DPSERVER_BASE64_SERVER2CLIENT) {
            $talkback = base64_encode($talkback);
        }
        $talkback .= chr(0);

        if (FALSE === socket_write($this->mMsgsock, $talkback,
                dp_strlen($talkback))) {
            echo sprintf(dp_text("socket_write(): failure [%u]: %s\n"),
                socket_last_error(), socket_strerror(socket_last_error()));
        }
    }

    /**
     * Shows some server info on the command line
     *
     * @param       object    &$universe  An instance of DpUniverse
     */
    private function _showStatus(&$universe)
    {
        if (DPSERVER_DEBUG_TYPE_MEMORY_GET_USAGE === DPSERVER_DEBUG_TYPE) {
            $universe->showStatusMemoryGetUsage();
        } elseif (DPSERVER_DEBUG_TYPE_GETRUSAGE === DPSERVER_DEBUG_TYPE) {
            $this->_showStatusMemoryGetrusage($universe);
        }
    }

    /**
     * Shows a line with getrusage info, see man getrusage
     *
     * @param       object    &$universe  An instance of DpUniverse
     */
    private function _showStatusMemoryGetrusage(&$universe)
    {
        if (!function_exists('getrusage')) {
            return;
        }

        $dat = getrusage();

        $utime_after = (int)$dat["ru_utime.tv_sec"]*1e6 +
            (int)$dat["ru_utime.tv_usec"];
        $stime_after = (int)$dat["ru_stime.tv_sec"]*1e6 +
            (int)$dat["ru_stime.tv_usec"];

        $dat["ru_utime"] = number_format(($utime_after - $this->mUtimeBefore) /
            1000000, 2) . 's';
        $dat["ru_stime"] = number_format(($stime_after - $this->mStimeBefore) /
            1000000, 2) . 's';
        $dat["ru_maxrss"] = $dat["ru_maxrss"] . 'kb';
        unset($dat['ru_oublock']);
        unset($dat['ru_inblock']);
        unset($dat['ru_nswap']);
        unset($dat['ru_nsignals']);
        unset($dat['ru_utime.tv_sec']);
        unset($dat['ru_utime.tv_usec']);
        unset($dat['ru_stime.tv_sec']);
        unset($dat['ru_stime.tv_usec']);

        if (0 === (int)$this->mShowedStatusHeader) {
            $keys = array_keys($dat);
            foreach ($keys as $i => $k) {
                $keys[$i] = dp_substr($keys[$i], 3);
                if (dp_strlen($keys[$i]) > 7) {
                    $keys[$i] = dp_substr($keys[$i], 0, 7);
                }
            }
            $this->mShowedStatusHeader = 30;

            echo sprintf(dp_text("Memory\t%s\n"), implode("\t", $keys));
        }

        echo sprintf(dp_text("%uKB\t%s\n"), round(memory_get_usage() / 1024),
            implode("\t", $dat));
        $this->mShowedStatusHeader--;
    }
}
?>
Return current item: DutchPIPE - Avatar worlds on websites