Location: PHPKode > scripts > floSocket > flosocket/floSocket.php
<?
/***************************************************************************
 *  Original floSocket copyright (C) 2005 by Joshua Hatfield.              *
 *                                                                         *
 *  In order to use any part of this floSocket Class, you must comply with *
 *  the license in 'license.doc'.  In particular, you may not remove this  *
 *  copyright notice.                                                      *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************

 - Product name: floSocket
 - Author: Joshua Hatfield (hide@address.com)
 - Release Version: 1.0.1
 - Release Date: 2005-07-22

Update 1.0.1: 2005-07-22
New Features
Added event SOCKET_EVENT_TICKERS
* Fires event after pushing buffer per channel.
Update to floSocket($address = false, $port = "9999", $tick = 250000)
* Added $tick paramater.  Specifies how long to wait between buffer checks.

Bug Fixes
Moved sleeping to inside the loop where it is actually useful.  

Changes
Default tick time changed to .25 seconds from 100 miliseconds or 0 if you 
  count the fact that the sleeping wasn't actually in the loop.  

Origional Release 1.0.0: 2005-07-01

Documentation:
First of all, please note that you must have the php installation configured 
with --enable-sockets.  If you are using this with clsDaemonize, you must also
have set --enable-pcntl, though that wasn't in the clsDaemonize documentation
at the time of this release.  This version has been written to be compatible 
with PHP 5.0 and later.  

This class is meant to emulate a multi-thread TCP TEXT server.  
As far as I'm concerned, there are only three events: 

*/
define('SOCKET_EVENT_CREATED', -1); // Fires on new connection before any data 
                                    // is received.
define('SOCKET_EVENT_NEWDATA',  1); // Fires on arrival of data, returning 
                                    // buffer, 1 byte at a time.
define('SOCKET_EVENT_EXPIREY',  0); // Fires on socket closure after flushing 
                                    // data.
define('SOCKET_EVENT_TICKERS',  2); // Fires after every buffer check also
                                    // after flushing buffer.  
/*

Assuming you create the object as such:
	$mySocket = new floSocket($address,$port);

You can set event handlers using
	$mySocket->set_handler(SOCKET_EVENT_CREATED, "fun_new_connection");
	$mySocket->set_handler(SOCKET_EVENT_NEWDATA, "fun_data_received");
	$mySocket->set_handler(SOCKET_EVENT_EXPIREY, "fun_connection_lost");
	$mySocket->set_handler(SOCKET_EVENT_TICKERS, "fun_tick");

And it will execute fun_new_connection(), fun_data_received() and 
fun_connection_lost() respectively upon event firing.  If the event for 
SOCKET_EVENT_NEWDATA is not defined, all data will be echoed.  Format for these 
functions MUST coincide with these definitions:

	fun_new_connection($channel) {}
	fun_data_received($channel, $buffer) {}
	fun_connection_lost($channel, $error) {}
	fun_tick($channel) {}

NOTE: These are not actual functions, they are examples of how event triggered
functions should be formatted.  $channel is channel index as integer, not a
valid php socket resource.  

The functions for use in this class are as follows:

	floSocket($address = false, $port = "9999")
		- Initialization.  If $address is omited, it will listen on all 
		  frequencies (ip addresses).  

	start()
		- Starts the listener based on the initialization information.

	stop($disconnect_message = "Server shutdown.", $error = false)
		- Cleanly disconnects everyone from the server triggering any
		  buffer flushing events and connection closure events that 
		  need to be triggered.  

	set_handler($handler_id, $handler_function)
		- Set the functions that process events as described above.  

	write($channel, $text)
		- Send text to a channel.  

	close($channel, $disconnect_message = "", $error = false)
		- Close a channel sending the disconnect message and returning
		  the error message if provided.  

	close_all($disconnect_message, $error = false)
		- Close each connection just like stop() except doesn't stop the
		  listener.  

	get_remote_address($channel)
		- Simple enough, returns the address.  

	get_remote_port($channel)
		- Return the remote port.

	get_socket_resource($channel)
		- Returns the actual socket resource for the channel because the
		  $channel variable is just an integer floSocket uses to track 
		  socket referrences.  

	set_echo($value = true)
		- Tell the server whether or not to auto-echo the received
		  buffer back to the peer.  If the SOCKET_EVENT_NEWDATA event 
		  is not set, there is nothing to do with the data so echo is
		  automatically turned ON.  To turn it off, you must specify a
		  SOCKET_EVENT_NEWDATA event.  

	function set_timeout($seconds = 60)
		- Set timeout in seconds.  By default there is no timeout, 
		  however, calling set_timeout without any value sets it to one
		  minute.  


*/

class floSocket {
	// Set up class variables.
	var $socketAddress;
	var $socketPort;
	var $sockets = array();
	var $event_handler = array();
	var $echoback = false;
	var $listening = false;
	var $failsafe_timeout = -1;
	var $tick_length;
	
	// Class initialization.  
	function floSocket($address = false, $port = "9999", $tick = 250000) {
		$this->socketAddress = $address;
		$this->socketPort = $port;
		$this->tick_length = $tick;
	}
	
	// Begin listening
	function start() {
		$mastersocket = false;
		// Check to see if already running listener.  
		if (!$this->listening) {
			// Creating master socket...
			if ($this->socketAddress === false) {
				$mastersocket = socket_create_listen($this->socketPort);
			} else {
				$mastersocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
				socket_connect($mastersocket, $dest, $port);
			}
			if ($mastersocket < 0) {
				return false;
			}
			if (@socket_bind($mastersocket, $this->socketAddress, $this->socketPort) < 0) {
				return false;
			}
			if (socket_listen($mastersocket) < 0) {
				return false;
			}
			if (!socket_set_option($mastersocket, SOL_SOCKET, SO_REUSEADDR, 1)) {
				return false;
			}
			if (!socket_set_nonblock($mastersocket)) {
				return false;
			}
			// End master socket creation.  
			
			//Begin listening.  
			$this->listening = true;
			$failsafe = 0;
			while($this->listening && ($this->failsafe_timeout < 0 || $failsafe++ <= $this->failsafe_timeout)){
				// Check for new socket arrival.  
				if ($newsocket = @socket_accept($mastersocket)) {
					// New connection, activate event and add channel.  
					socket_set_nonblock($newsocket);
					$this->sockets[] = $newsocket;
					$channel = array_pop(array_keys($this->sockets));
					$proc_event = $this->event_handler[SOCKET_EVENT_CREATED];
					if ($proc_event) $proc_event($channel, $error);
					$newsocket = false;
				}
				// Check for new data on existing (and new) sockets.
				foreach($this->sockets as $channel => $socket) {
					while ($status = @socket_recv($socket, $buffer, 1, 0)) {
						// Data received, activate event.  
						$proc_event = $this->event_handler[SOCKET_EVENT_NEWDATA];
						if ($proc_event) $proc_event($channel, $buffer);
						if (!$proc_event || $this->echoback) {
							$this->write($channel, $buffer);
						}
					}
					$proc_event = $this->event_handler[SOCKET_EVENT_TICKERS];
					if ($proc_event) $proc_event($channel);
					if ($status === 0) {
						// Socket error.  Remote disconnection.  
						$this->close($channel, "Remote connection vanished.");
					}
				}
				// Sleep for a tad so other processes can get their proc time.
				usleep($this->tick_length);
			} // end while
		} else {
			// Return false if already running.  
			return false;
		}
		// Shutdown listener as stop() has been detected or failsafe 
		// timeout expired.  
		socket_shutdown($mastersocket);
		socket_close($mastersocket);
		return true;
	}
	
	// Stop listener cleanly.  
	function stop($disconnect_message = "Server shutdown.", $error = false) {
		$this->close_all($disconnect_message, $error = false);
		$this->listening = false;
	}
	
	// Set event handlers
	function set_handler($handler_id, $handler_function) {
		$this->event_handler[$handler_id] = $handler_function;
	}
	
	// Send data to active channel.  
	function write($channel, $text) {
		if (isset($this->sockets[$channel]) && socket_write($this->sockets[$channel], $text, strlen($text)) < 0) {
			// Close channel and return false on failure.  
			$this->close($this->sockets[$channel], "", "Socket error while sending...");
			return false;
		}
		return true;
	}
	
	// Close channel.  
	function close($channel, $disconnect_message = "", $error = false) {
		if (isset($this->sockets[$channel])) {
			while ($status = @socket_recv($socket, $buffer, 1, 0)) {
				// Activate event for any leftover buffer data.  
				$proc_event = $this->event_handler[SOCKET_EVENT_NEWDATA];
				if ($proc_event) $proc_event($channel, $buffer);
				if (!$proc_event || $this->echoback) {
					$this->write($channel, $buffer);
				}
			}
			// Send disconnect message.  
			socket_write($this->sockets[$channel], $disconnect_message, strlen($disconnect_message));
			
			// Activate closure event.  
			$proc_event = $this->event_handler[SOCKET_EVENT_EXPIREY];
			if ($proc_event) $proc_event($channel, $error);
			
			// Shutdown socket.  
			socket_shutdown($this->sockets[$channel]);
			socket_close($this->sockets[$channel]);
			
			// Clear channel variable.  
			unset($this->sockets[$channel]);
		}
		return $error;
	}
	
	// Systematically close all conenctions.  
	function close_all($disconnect_message, $error = false) {
		foreach($this->sockets as $key => $value) {
			$this->close($key, $disconnect_message, $error = false);
		}
	}
	
	// Get the remote IP address.  
	function get_remote_address($channel) {
		if (isset($this->sockets[$channel])) {
			socket_getpeername($this->sockets[$channel], $remoteaddress, $remoteport);
			return $remoteaddress;
		}
	}
	
	// Get the remote IP port.  
	function get_remote_port($channel) {
		if (isset($this->sockets[$channel])) {
			socket_getpeername($this->sockets[$channel], $remoteaddress, $remoteport);
			return $remoteport;
		}
	}
	
	// Get php socket resource (for those who like more control).  
	function get_socket_resource($channel) {
		return $this->sockets[$channel];
	}
	
	// Set echoback.  
	function set_echo($value = true) {
		$this->echoback = $value;
	}
	
	// Set timeout.  
	function set_timeout($seconds = 60) {
		$this->failsafe_timeout = $seconds * 1000000 / $this->tick_length;
	}
}
?>
Return current item: floSocket