cvs: pear /Net_Server Server.php /Net_Server/Server Handler.php /Net_Server/Server/Drivers Fork.php Sequential.php /Net_Server/examples
talkback.php

From: Date: Tue, 09 Sep 2003 07:17:59 +0000
Subject: cvs: pear /Net_Server Server.php /Net_Server/Server Handler.php /Net_Server/Server/Drivers Fork.php Sequential.php /Net_Server/examples
talkback.php
Groups: php.pear.cvs 
Request: Send a blank email to pear-cvs+get-12566@lists.php.net to get a copy of this message
schst Tue Sep 9 03:17:59 2003 EDT Added files: /pear/Net_Server/Server Handler.php /pear/Net_Server/Server/Drivers Fork.php Sequential.php /pear/Net_Server/examples talkback.php Modified files: /pear/Net_Server Server.php Log: breaks BC added drivers: sequential and fork added new concpt of handlers instead of extending the class added handler base class added talkback example

Index: pear/Net_Server/Server.php diff -u pear/Net_Server/Server.php:1.1.1.1 pear/Net_Server/Server.php:1.2 --- pear/Net_Server/Server.php:1.1.1.1 Sat Aug 30 04:13:40 2003 +++ pear/Net_Server/Server.php Tue Sep 9 03:17:56 2003 @@ -13,115 +13,134 @@ // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ -// | Authors: Stephan Schmidt <schst@php-tools.net> | +// | Authors: Stephan Schmidt <schst@php.net> | // +----------------------------------------------------------------------+ // -// $Id: Server.php,v 1.1.1.1 2003/08/30 08:13:40 schst Exp $ +// $Id: Server.php,v 1.2 2003/09/09 07:17:56 schst Exp $ require_once 'PEAR.php'; /** - * Net_Server * PHP socket server base class - * This class is a pearified version of patServer (http://www.php-tools.de) - * To create your own server, extend this class and implement callbacks for the events - * you need * - * Events that can be handled: - * * onStart - * * onConnect - * * onConnectionRefused - * * onClose - * * onShutdown - * * onReceiveData + * This class must only be used to create a new server using + * the static method 'create()'. * - * @version 0.9.1b - * @author Stephan Schmidt <schst@php-tools.de> + * To handle the events that happen while the server is running + * you have to create a new class that handles all events. + * + * <code> + * require_once 'myHandler.php'; + * require_once 'Net/Server.php'; + * + * $server = &Net_Server::create('fork', 'localhost', 9090); + * + * $handler = &new myHandler; + * + * $server->setCallbackObject($handler); + * + * $server->start(); + * </code> + * + * See Server/Handler.php for a baseclass that you can + * use to implement new handlers. + * + * @version 1.0alpha + * @author Stephan Schmidt <schst@php.net> */ class Net_Server extends PEAR { - /** * port to listen + * @access private * @var integer $port */ var $port = 10000; /** * domain to bind to + * @access private * @var string $domain */ var $domain = "localhost"; /** + * all file descriptors are stored here + * @access private + * @var array $clientFD + */ + var $clientFD = array(); + + /** * maximum amount of clients + * @access private * @var integer $maxClients */ var $maxClients = -1; /** * buffer size for socket_read + * @access private * @var integer $readBufferSize */ var $readBufferSize = 128; /** * end character for socket_read + * @access private * @var integer $readEndCharacter */ var $readEndCharacter = "\n"; /** * maximum of backlog in queue + * @access private * @var integer $maxQueue */ var $maxQueue = 500; /** * debug mode + * @access private * @var boolean $_debug */ - var $_debug = false; + var $_debug = true; /** * debug mode, normally only text is needed, as servers should not be run in a browser + * @access private * @var string $_debugMode */ var $_debugMode = "text"; /** * debug destination (filename or stdout) + * @access private * @var string $_debugDest */ var $_debugDest = "stdout"; /** * empty array, used for socket_select + * @access private * @var array $null */ var $null = array(); /** - * all file descriptors are stored here - * @var array $clientFD - */ - var $clientFD = array(); - - /** * needed to store client information + * @access private * @var array $clientInfo */ var $clientInfo = array(); /** - * amount of clients - * @var integer $clients - */ - var $clients = 0; - - /** - * create a new socket server + * constructor _MUST_ not be called directly * - * @access public + * instead please use the Net_Server::create() method + * that can be called statically and will return a server + * of the specified type + * + * @access private * @param string $domain domain to bind to * @param integer $port port to listen to */ @@ -137,17 +156,53 @@ } /** - * set maximum amount of simultaneous connections + * destructor * - * @access public - * @param int $maxClients + * @access private */ - function setMaxClients($maxClients) + function _Net_Server() { - $this->maxClients = $maxClients; + $this->shutdown(); } /** + * create a new server + * + * Currently two types of servers are supported: + * - 'sequential', creates a server where one process handles all request from all clients sequentially + * - 'fork', creates a server where a new process is forked for each client that connects to the server. This only works on *NIX + * + * @access public + * @static + * @param string $type type of the server + * @param string $host hostname + * @param integer $port port + */ + function &create($type, $host, $port) + { + if (!function_exists('socket_create')) { + return $this->raiseError('Sockets extension not available.'); + } + + $type = ucfirst(strtolower($type)); + $driverFile = 'Net/Server/Drivers/' . $type . '.php'; + $className = 'Net_Server_' . $type; + + if (!file_exists($driverFile)) { + return PEAR::raiseError('Unknown server type'); + } + + include_once $driverFile; + + if (!class_exists($className)) { + return PEAR::raiseError('Driver file is corrupt.'); + } + + $server = &new $className($host, $port); + return $server; + } + + /** * set debug mode * * @access public @@ -166,112 +221,6 @@ $this->_debugDest = $dest; } - /** - * start the server - * - * @access public - */ - function start() - { - $this->initFD = @socket_create(AF_INET, SOCK_STREAM, 0); - if (!$this->initFD) { - return $this->raiseError("Could not create socket."); - } - - // adress may be reused - socket_setopt($this->initFD, SOL_SOCKET, SO_REUSEADDR, 1); - - // bind the socket - if (!@socket_bind($this->initFD, $this->domain, $this->port)) { - $error = $this->_getLastSocketError($this->initFd); - @socket_close($this->initFD); - return $this->raiseError("Could not bind socket to ".$this->domain." on port ".$this->port." (".$error.")."); - } - - // listen on selected port - if (!@socket_listen($this->initFD, $this->maxQueue)) { - $error = $this->_getLastSocketError($this->initFd); - @socket_close($this->initFD); - return $this->raiseError("Could not listen (".$error.")."); - } - - $this->_sendDebugMessage("Listening on port ".$this->port.". Server started at ".date("H:i:s", time())); - - // this allows the shutdown function to check whether the server is already shut down - $GLOBALS["_Net_Server_Status"] = "running"; - // this ensures that the server will be sutdown correctly - register_shutdown_function(array($this, "shutdown")); - - if (method_exists($this, "onStart")) { - $this->onStart(); - } - - while(true) - { - $readFDs = array(); - array_push($readFDs, $this->initFD); - - // fetch all clients that are awaiting connections - for($i = 0; $i < count($this->clientFD); $i++) { - if (isset($this->clientFD[$i])) - array_push($readFDs, $this->clientFD[$i]); - } - - // block and wait for data or new connection - $ready = @socket_select($readFDs, $this->null, $this->null, NULL); - - if ($ready === false) { - $this->_sendDebugMessage("socket_select failed."); - $this->shutdown(); - } - - // check for new connection - if (in_array($this->initFD, $readFDs)) { - $newClient = $this->acceptConnection($this->initFD); - - // check for maximum amount of connections - if ($this->maxClients > 0) { - if ($this->clients > $this->maxClients) { - $this->_sendDebugMessage("Too many connections."); - - if (method_exists($this, "onConnectionRefused")) { - $this->onConnectionRefused($newClient); - } - - $this->closeConnection($newClient); - } - } - - if (--$ready <= 0) { - continue; - } - } - - // check all clients for incoming data - for($i = 0; $i < count($this->clientFD); $i++) { - if (!isset($this->clientFD[$i])) { - continue; - } - - if (in_array($this->clientFD[$i], $readFDs)) { - $data = $this->readFromSocket($i); - - // empty data => connection was closed - if (!$data) { - $this->_sendDebugMessage("Connection closed by peer"); - $this->closeConnection($i); - } - else { - $this->_sendDebugMessage("Received ".trim($data)." from ".$i); - - if (method_exists($this, "onReceiveData")) { - $this->onReceiveData($i, $data); - } - } - } - } - } - } /** * read from a socket @@ -280,7 +229,7 @@ * @param integer $clientId internal id of the client to read from * @return string $data data that was read */ - function readFromSocket($clientId) { + function readFromSocket($clientId = 0) { // start with empty string $data = ""; @@ -298,182 +247,13 @@ } if ($buf === false) { - $this->_sendDebugMessage("Could not read from client ".$clientId." (".$this->_getLastSocketError($this->clientFD[$clientId]).")."); + $this->_sendDebugMessage("Could not read from client ".$clientId." (".$this->getLastSocketError($this->clientFD[$clientId]).")."); } - $this->clientInfo[$clientId]["bytesReceived"] = $this->clientInfo[$clientId]["bytesReceive"] + strlen($data); - return $data; } - - /** - * accept a new connection - * - * @access public - * @param resource &$socket socket that received the new connection - * @return int $clientID internal ID of the client - */ - function acceptConnection(&$socket) { - for($i = 0 ; $i <= count($this->clientFD); $i++) { - if (!isset($this->clientFD[$i]) || $this->clientFD[$i] == NULL) { - $this->clientFD[$i] = socket_accept($socket); - socket_setopt($this->clientFD[$i], SOL_SOCKET, SO_REUSEADDR, 1); - $peer_host = ""; - $peer_port = ""; - socket_getpeername($this->clientFD[$i], $peer_host, $peer_port); - $this->clientInfo[$i] = array( - "host" => $peer_host, - "port" => $peer_port, - "connectOn" => time(), - "bytesSent" => 0, - "bytesReceived" => 0 - ); - $this->clients++; - - $this->_sendDebugMessage("New connection (".$i.") from ".$peer_host." on port ".$peer_port); - - if (method_exists($this, "onConnect")) { - $this->onConnect($i); - } - return $i; - } - } - } - - /** - * check, whether a client is still connected - * - * @access public - * @param integer $id client id - * @return boolean $connected true if client is connected, false otherwise - */ - function isConnected($id) { - if (!isset($this->clientFD[$id])) { - return false; - } - return true; - } - - /** - * close connection to a client - * - * @access public - * @param int $clientID internal ID of the client - */ - function closeConnection($id) { - if (!isset($this->clientFD[$id])) { - return $this->raiseError( "Connection already has been closed." ); - } - - if (method_exists($this, "onClose")) { - $this->onClose($id); - } - - $this->_sendDebugMessage("Closed connection (".$id.") from ".$this->clientInfo[$id]["host"]." on port ".$this->clientInfo[$id]["port"]); - - @socket_close($this->clientFD[$id]); - $this->clientFD[$id] = NULL; - unset($this->clientInfo[$id]); - $this->clients--; - } - - /** - * shutdown server - * - * @access public - */ - function shutDown() { - if ($GLOBALS["_Net_Server_Status"] != "running") { - exit; - } - $GLOBALS["_Net_Server_Status"] = "stopped"; - - if (method_exists($this, "onShutdown")) { - $this->onShutdown(); - } - - $maxFD = count($this->clientFD); - for($i = 0; $i < $maxFD; $i++) { - $this->closeConnection($i); - } - - @socket_close($this->initFD); - - $this->_sendDebugMessage("Shutdown server."); - exit; - } /** - * get current amount of clients - * - * @access public - * @return int $clients amount of clients - */ - function getClients() { - return $this->clients; - } - - /** - * send data to a client - * - * @access public - * @param int $clientId ID of the client - * @param string $data data to send - * @param boolean $debugData flag to indicate whether data that is written to socket should also be sent as debug message - */ - function sendData($clientId, $data, $debugData = true) { - if (!isset($this->clientFD[$clientId]) || $this->clientFD[$clientId] == NULL) { - return $this->raiseError("Client does not exist."); - } - - if ($debugData) { - $this->_sendDebugMessage("sending: \"" . $data . "\" to: $clientId" ); - } - if (!@socket_write($this->clientFD[$clientId], $data)) { - $this->_sendDebugMessage("Could not write '".$data."' client ".$clientId." (".$this->_getLastSocketError($this->clientFD[$clientId]).")."); - } - $this->clientInfo[$clientId]["bytesSent"] = $this->clientInfo[$clientId]["bytesSent"] + strlen($data); - } - - /** - * send data to all clients - * - * @access public - * @param string $data data to send - * @param array $exclude client ids to exclude - */ - function broadcastData($data, $exclude = array()) { - if (!empty($exclude) && !is_array($exclude)) { - $exclude = array($exclude); - } - - $bytes = strlen($data); - - for($i = 0; $i < count($this->clientFD); $i++) { - if (isset($this->clientFD[$i]) && $this->clientFD[$i] != NULL && !in_array($i, $exclude)) { - if (!@socket_write($this->clientFD[$i], $data)) { - $this->_sendDebugMessage("Could not write '".$data."' client ".$i." (".$this->_getLastSocketError($this->clientFD[$i]).")."); - } - $this->clientInfo[$i]["bytesSent"] = $this->clientInfo[$i]["bytesSent"] + $bytes; - } - } - } - - /** - * get current information about a client - * - * @access public - * @param int $clientId ID of the client - * @return array $info information about the client - */ - function getClientInfo($clientId) { - if (!isset($this->clientFD[$clientId]) || $this->clientFD[$clientId] == NULL) { - return $this->raiseError("Client does not exist."); - } - return $this->clientInfo[$clientId]; - } - - /** * send a debug message * * @access private @@ -495,7 +275,7 @@ break; } - if ($this->_debugDest == "stdout" || empty($this->debugDest)) { + if ($this->_debugDest == "stdout" || empty($this->_debugDest)) { echo $msg; flush(); return true; @@ -506,12 +286,26 @@ } /** + * register a callback object, that is used to handle all events + * + * @access public + * @param object $object callback object + */ + function setCallbackObject(&$object) + { + $this->callbackObj = &$object; + if (method_exists($this->callbackObj,'setServerReference')) { + $this->callbackObj->setServerReference($this); + } + } + + /** * return string for last socket error * * @access public * @return string $error last error */ - function _getLastSocketError(&$fd) { + function getLastSocketError(&$fd) { if(!is_resource($fd)) { return ""; } Index: pear/Net_Server/Server/Handler.php +++ pear/Net_Server/Server/Handler.php <?PHP /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.0 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Authors: Stephan Schmidt <schst@php.net> | // +----------------------------------------------------------------------+ // // $Id: Handler.php,v 1.1 2003/09/09 07:17:57 schst Exp $ require_once 'PEAR.php'; /** * Base class for all handlers * * @version 1.0 * @author Stephan Schmidt <schst@php.net> */ class Net_Server_Handler { /** * reference to the server object, used to send data to the client * @var object */ var $_server; /** * set a reference to the server object * * This is done automatically when the handler is passed over to the server * * @access public * @param object Net_Server_* object */ function setServerReference( &$server ) { $this->_server = &$server; } /** * onStart handler * * This handler is called, when the server starts. * Available in: * - Net_Server_Sequential * - Net_Server_Fork * * @access public */ function onStart() { } /** * onShutdown handler * * This handler is called, when the server is stopped. * Available in: * - Net_Server_Sequential * * @access public */ function onShutdown() { } /** * onConnect handler * * This handler is called, when a new client connects * Available in: * - Net_Server_Sequential * - Net_Server_Fork * * @access public * @param integer $clientId unique id of the client, in Net_Server_Fork, this is always 0 */ function onConnect($clientId = 0) { } /** * onConnectionRefused handler * * This handler is called, when a new client tries to connect but is not allowed to * Available in: * - Net_Server_Sequential * * @access public * @param integer $clientId unique id of the client */ function onConnectionRefused($clientId = 0) { } /** * onClose handler * * This handler is called, when a client disconnects from the server * Available in: * - Net_Server_Sequential * - Net_Server_Fork * * @access public * @param integer $clientId unique id of the client, in Net_Server_Fork, this is always 0 */ function onClose($clientId = 0) { } /** * onReceiveData handler * * This handler is called, when a client sends data to the server * Available in: * - Net_Server_Sequential * - Net_Server_Fork * * @access public * @param integer $clientId unique id of the client, in Net_Server_Fork, this is always 0 * @param string $data data that the client sent */ function onReceiveData($clientId = 0, $data = "") { } } ?> Index: pear/Net_Server/Server/Drivers/Fork.php +++ pear/Net_Server/Server/Drivers/Fork.php <?PHP /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.0 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Authors: Stephan Schmidt <schst@php.net> | // +----------------------------------------------------------------------+ // // $Id: Fork.php,v 1.1 2003/09/09 07:17:58 schst Exp $ /** * Forking server class. * * This class will fork a new process for each connection. * This allows you to build servers, where communication between * the clients is no issue. * * Events that can be handled: * - onStart * - onConnect * - onClose * - onReceiveData * * @version 1.0alpha * @author Stephan Schmidt <schst@php.net> */ class Net_Server_Fork extends Net_Server { /** * flag to indicate whether this is the parent * @access private * @var boolean */ var $_isParent = true; /** * set maximum amount of simultaneous connections * * this is not possible as each client gets its own * process * * @access public * @param int $maxClients */ function setMaxClients($maxClients) { return $this->raiseError('Not implemented'); } /** * start the server * * @access public */ function start() { if (!function_exists('pcntl_fork')) { return $this->raiseError('Needs pcntl extension to fork processes.'); } $this->initFD = @socket_create(AF_INET, SOCK_STREAM, 0); if (!$this->initFD) { return $this->raiseError("Could not create socket."); } // adress may be reused socket_setopt($this->initFD, SOL_SOCKET, SO_REUSEADDR, 1); // bind the socket if (!@socket_bind($this->initFD, $this->domain, $this->port)) { $error = $this->getLastSocketError($this->initFd); @socket_close($this->initFD); return $this->raiseError("Could not bind socket to ".$this->domain." on port ".$this->port." (".$error.")."); } // listen on selected port if (!@socket_listen($this->initFD, $this->maxQueue)) { $error = $this->getLastSocketError($this->initFd); @socket_close($this->initFD); return $this->raiseError("Could not listen (".$error.")."); } $this->_sendDebugMessage("Listening on port ".$this->port.". Server started at ".date("H:i:s", time())); if (method_exists($this->callbackObj, "onStart")) { $this->callbackObj->onStart(); } // Dear children, please do not become zombies pcntl_signal(SIGCHLD, SIG_IGN); // wait for incmoning connections while (true) { // new connection if(($fd = socket_accept($this->initFD))) { $pid = pcntl_fork(); if($pid == -1) { return $this->raiseError('Could not fork child process.'); } // This is the child => handle the request elseif($pid == 0) { // this is not the parent $this->_isParent = false; // store the new file descriptor $this->clientFD[0] = $fd; $peer_host = ""; $peer_port = ""; socket_getpeername($this->clientFD[0], $peer_host, $peer_port); $this->clientInfo[0] = array( "host" => $peer_host, "port" => $peer_port, "connectOn" => time() ); $this->_sendDebugMessage("New connection from ".$peer_host." on port ".$peer_port); if (method_exists($this->callbackObj, "onConnect")) { $this->callbackObj->onConnect($i); } $this->serviceRequest(); $this->closeConnection(); exit; } else { // the parent process does not have to do anything } } } } /** * service the current request * * * */ function serviceRequest() { while( true ) { $readFDs = array( $this->clientFD[0] ); // block and wait for data $ready = @socket_select($readFDs, $this->null, $this->null, NULL); if ($ready === false) { $this->_sendDebugMessage("socket_select failed."); $this->shutdown(); } if (in_array($this->clientFD[0], $readFDs)) { $data = $this->readFromSocket(); // empty data => connection was closed if (!$data) { $this->_sendDebugMessage("Connection closed by peer"); $this->closeConnection(); } else { $this->_sendDebugMessage("Received ".trim($data)." from ".$i); if (method_exists($this->callbackObj, "onReceiveData")) { $this->callbackObj->onReceiveData(0, $data); } } } } } /** * check, whether a client is still connected * * @access public * @param integer $id client id * @return boolean $connected true if client is connected, false otherwise */ function isConnected() { if (is_resource($this->clientFD[0])) { return true; } } /** * get current amount of clients * * not possible with forking * * @access public * @return PEAR_Error */ function getClients() { return $this->raiseError('Not implemented'); } /** * send data to a client * * @access public * @param string $data data to send * @param boolean $debugData flag to indicate whether data that is written to socket should also be sent as debug message */ function sendData($data, $debugData = true) { // keep it compatible to Net_Server_Sequential if (is_string($debugData)) { $data = $debugData; } if (!isset($this->clientFD[0]) || $this->clientFD[0] == NULL) { return $this->raiseError("Client does not exist."); } if ($debugData) { $this->_sendDebugMessage("sending: \"" . $data . "\" to: $clientId" ); } if (!@socket_write($this->clientFD[0], $data)) { $this->_sendDebugMessage("Could not write '".$data."' client ".$clientId." (".$this->getLastSocketError($this->clientFD[$clientId]).")."); } } /** * send data to all clients * * @access public * @param string $data data to send * @param array $exclude client ids to exclude */ function broadcastData($data, $exclude = array()) { $this->sendData($data); } /** * get current information about a client * * @access public * @return array $info information about the client */ function getClientInfo() { if (!isset($this->clientFD[0]) || $this->clientFD[0] == NULL) { return $this->raiseError("Client does not exist."); } return $this->clientInfo[$clientId]; } /** * close the current connection * * @access public */ function closeConnection() { if (!isset($this->clientFD[0])) { return $this->raiseError( "Connection already has been closed." ); } if (method_exists($this->callbackObj, "onClose")) { $this->callbackObj->onClose($id); } $this->_sendDebugMessage("Closed connection from ".$this->clientInfo[0]["host"]." on port ".$this->clientInfo[0]["port"]); @socket_close($this->clientFD[0]); $this->clientFD[0] = NULL; unset($this->clientInfo[0]); } /** * shutdown server * * @access public */ function shutDown() { $this->closeConnection(); exit; } } ?> Index: pear/Net_Server/Server/Drivers/Sequential.php +++ pear/Net_Server/Server/Drivers/Sequential.php <?PHP /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 4 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2002 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 2.0 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available at through the world-wide-web at | // | http://www.php.net/license/2_02.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Authors: Stephan Schmidt <schst@php.net> | // +----------------------------------------------------------------------+ // // $Id: Sequential.php,v 1.1 2003/09/09 07:17:58 schst Exp $ /** * Sequential server class. * * This class will handles all connection in one server process. * This allows you to build servers, where communication between * the clients is easy. The drawback is that clients are served * sequentially (hence the name). If you send large blocks of data * to a client, the others will have to wait. * For servers where communication between clients is not needed, * use Net_Server_Fork instead. * * Events that can be handled: * - onStart * - onConnect * - onConnectionRefused * - onClose * - onReceiveData * - onShutdown * * @version 1.0alpha * @author Stephan Schmidt <schst@php.net> */ class Net_Server_Sequential extends Net_Server { /** * amount of clients * @var integer $clients */ var $clients = 0; /** * set maximum amount of simultaneous connections * * @access public * @param int $maxClients */ function setMaxClients($maxClients) { $this->maxClients = $maxClients; } /** * start the server * * @access public */ function start() { $this->initFD = @socket_create(AF_INET, SOCK_STREAM, 0); if (!$this->initFD) { return $this->raiseError("Could not create socket."); } // adress may be reused socket_setopt($this->initFD, SOL_SOCKET, SO_REUSEADDR, 1); // bind the socket if (!@socket_bind($this->initFD, $this->domain, $this->port)) { $error = $this->getLastSocketError($this->initFd); @socket_close($this->initFD); return $this->raiseError("Could not bind socket to ".$this->domain." on port ".$this->port." (".$error.")."); } // listen on selected port if (!@socket_listen($this->initFD, $this->maxQueue)) { $error = $this->getLastSocketError($this->initFd); @socket_close($this->initFD); return $this->raiseError("Could not listen (".$error.")."); } $this->_sendDebugMessage("Listening on port ".$this->port.". Server started at ".date("H:i:s", time())); // this allows the shutdown function to check whether the server is already shut down $GLOBALS["_Net_Server_Status"] = "running"; if (method_exists($this->callbackObj, "onStart")) { $this->callbackObj->onStart(); } while (true) { $readFDs = array(); array_push($readFDs, $this->initFD); // fetch all clients that are awaiting connections for ($i = 0; $i < count($this->clientFD); $i++) { if (isset($this->clientFD[$i])) array_push($readFDs, $this->clientFD[$i]); } // block and wait for data or new connection $ready = @socket_select($readFDs, $this->null, $this->null, NULL); if ($ready === false) { $this->_sendDebugMessage("socket_select failed."); $this->shutdown(); } // check for new connection if (in_array($this->initFD, $readFDs)) { $newClient = $this->acceptConnection($this->initFD); // check for maximum amount of connections if ($this->maxClients > 0) { if ($this->clients > $this->maxClients) { $this->_sendDebugMessage("Too many connections."); if (method_exists($this->callbackObj, "onConnectionRefused")) { $this->callbackObj->onConnectionRefused($newClient); } $this->closeConnection($newClient); } } if (--$ready <= 0) { continue; } } // check all clients for incoming data for($i = 0; $i < count($this->clientFD); $i++) { if (!isset($this->clientFD[$i])) { continue; } if (in_array($this->clientFD[$i], $readFDs)) { $data = $this->readFromSocket($i); // empty data => connection was closed if (!$data) { $this->_sendDebugMessage("Connection closed by peer"); $this->closeConnection($i); } else { $this->_sendDebugMessage("Received ".trim($data)." from ".$i); if (method_exists($this->callbackObj, "onReceiveData")) { $this->callbackObj->onReceiveData($i, $data); } } } } } } /** * accept a new connection * * @access public * @param resource &$socket socket that received the new connection * @return int $clientID internal ID of the client */ function acceptConnection(&$socket) { for($i = 0 ; $i <= count($this->clientFD); $i++) { if (!isset($this->clientFD[$i]) || $this->clientFD[$i] == NULL) { $this->clientFD[$i] = socket_accept($socket); socket_setopt($this->clientFD[$i], SOL_SOCKET, SO_REUSEADDR, 1); $peer_host = ""; $peer_port = ""; socket_getpeername($this->clientFD[$i], $peer_host, $peer_port); $this->clientInfo[$i] = array( "host" => $peer_host, "port" => $peer_port, "connectOn" => time() ); $this->clients++; $this->_sendDebugMessage("New connection (".$i.") from ".$peer_host." on port ".$peer_port); if (method_exists($this->callbackObj, "onConnect")) { $this->callbackObj->onConnect($i); } return $i; } } } /** * check, whether a client is still connected * * @access public * @param integer $id client id * @return boolean $connected true if client is connected, false otherwise */ function isConnected($id) { if (!isset($this->clientFD[$id])) { return false; } return true; } /** * get current amount of clients * * @access public * @return int $clients amount of clients */ function getClients() { return $this->clients; } /** * send data to a client * * @access public * @param int $clientId ID of the client * @param string $data data to send * @param boolean $debugData flag to indicate whether data that is written to socket should also be sent as debug message */ function sendData($clientId, $data, $debugData = true) { if (!isset($this->clientFD[$clientId]) || $this->clientFD[$clientId] == NULL) { return $this->raiseError("Client does not exist."); } if ($debugData) { $this->_sendDebugMessage("sending: \"" . $data . "\" to: $clientId" ); } if (!@socket_write($this->clientFD[$clientId], $data)) { $this->_sendDebugMessage("Could not write '".$data."' client ".$clientId." (".$this->getLastSocketError($this->clientFD[$clientId]).")."); } } /** * send data to all clients * * @access public * @param string $data data to send * @param array $exclude client ids to exclude */ function broadcastData($data, $exclude = array()) { if (!empty($exclude) && !is_array($exclude)) { $exclude = array($exclude); } for($i = 0; $i < count($this->clientFD); $i++) { if (isset($this->clientFD[$i]) && $this->clientFD[$i] != NULL && !in_array($i, $exclude)) { if (!@socket_write($this->clientFD[$i], $data)) { $this->_sendDebugMessage("Could not write '".$data."' client ".$i." (".$this->getLastSocketError($this->clientFD[$i]).")."); } } } } /** * get current information about a client * * @access public * @param int $clientId ID of the client * @return array $info information about the client */ function getClientInfo($clientId) { if (!isset($this->clientFD[$clientId]) || $this->clientFD[$clientId] == NULL) { return $this->raiseError("Client does not exist."); } return $this->clientInfo[$clientId]; } /** * close connection to a client * * @access public * @param int $clientID internal ID of the client */ function closeConnection($id = 0) { if (!isset($this->clientFD[$id])) { return $this->raiseError( "Connection already has been closed." ); } if (method_exists($this->callbackObj, "onClose")) { $this->callbackObj->onClose($id); } $this->_sendDebugMessage("Closed connection (".$id.") from ".$this->clientInfo[$id]["host"]." on port ".$this->clientInfo[$id]["port"]); @socket_close($this->clientFD[$id]); $this->clientFD[$id] = NULL; unset($this->clientInfo[$id]); $this->clients--; } /** * shutdown server * * @access public */ function shutDown() { if ($GLOBALS["_Net_Server_Status"] != "running") { exit; } $GLOBALS["_Net_Server_Status"] = "stopped"; if (method_exists($this->callbackObj, "onShutdown")) { $this->callbackObj->onShutdown(); } $maxFD = count($this->clientFD); for($i = 0; $i < $maxFD; $i++) { $this->closeConnection($i); } @socket_close($this->initFD); $this->_sendDebugMessage("Shutdown server."); exit; } } ?> Index: pear/Net_Server/examples/talkback.php +++ pear/Net_Server/examples/talkback.php #!/usr/local/bin/php <?PHP // server base class require_once 'Net/Server.php'; // base class for the hanlder require_once 'Net/Server/Handler.php'; /** * simple example that implements a talkback. * * Normally this should be a bit more code and in a separate file * * @version 0.1 * @author Stephan Schmidt <schst@php.net> */ class Net_Server_Handler_Talkback extends Net_Server_Handler { /** * If the user sends data, send it back to him * * @access public * @param integer $clientId * @param string $data */ function onReceiveData( $clientId = 0, $data = "" ) { $this->_server->sendData( $clientId, "You said: $data" ); } } // create a server that forks new processes $server = &Net_Server::create('fork', 'localhost', 9090); $handler = &new Net_Server_Handler_Talkback; // hand over the object that handles server events $server->setCallbackObject($handler); // start the server $server->start(); ?>
« previous php.pear.cvs (#12566) next »