<?php
/*
Todo: binary_mode
public $binary = true;
socket_read($this->socket, $length, PHP_BINARY_READ);
*/
/**
* |--------------|---------------------------------|
* | | Multithreaded |
* | Function |------------------|--------------|
* | | Activated | Deactivated |
* |--------------|------------------|--------------|
* | SendMessage | $client, $string | $string |
* |--------------|------------------|--------------|
* | GetClientIP | $client | no args |
* |--------------|------------------|--------------|
* |----------------------------------------|-------------------------------------------------------------------|
* | | Multithreaded |
* | Notification handler |--------------------------------------|----------------------------|
* | | Activated | Deactivated |
* |----------------------------------------|--------------------------------------|----------------------------|
* | NOTIFICATION_SERVER_START_SUCCEEDED | no args | no args |
* |----------------------------------------|--------------------------------------|----------------------------|
* | NOTIFICATION_SERVER_START_FAILED | $error_code, $error_string | $error_code, $error_string |
* |----------------------------------------|--------------------------------------|----------------------------|
* | NOTIFICATION_SERVER_SHUTDOWN_SUCCEEDED | no args | no args |
* |----------------------------------------|--------------------------------------|----------------------------|
* | NOTIFICATION_SERVER_SHUTDOWN_FAILED | $error_code, $error_string | $error_code, $error_string |
* |----------------------------------------|--------------------------------------|----------------------------|
* | NOTIFICATION_MESSAGE_SENT | $client | no args |
* |----------------------------------------|--------------------------------------|----------------------------|
* | NOTIFICATION_MESSAGE_NOT_SEND | $client, $error_code, $error_string | $error_code, $error_string |
* |----------------------------------------|--------------------------------------|----------------------------|
* | NOTIFICATION_CLIENT_CONNECTED | $client | n/a |
* |----------------------------------------|--------------------------------------|----------------------------|
* | NOTIFICATION_CLIENT_DISCONNECTED | $client | no args |
* |----------------------------------------|--------------------------------------|----------------------------|
* | NOTIFICATION_MESSAGE_RECEIVED | $client, $string | $string |
* |----------------------------------------|--------------------------------------|----------------------------|
* | NOTIFICATION_MESSAGE_END | $client | no args |
* |----------------------------------------|--------------------------------------|----------------------------|
*
*/
define('NOTIFICATION_SERVER_START_SUCCEEDED', 1);
define('NOTIFICATION_SERVER_START_FAILED', 2);
define('NOTIFICATION_SERVER_SHUTDOWN_SUCCEEDED', 3);
define('NOTIFICATION_SERVER_SHUTDOWN_FAILED', 4);
define('NOTIFICATION_MESSAGE_SENT', 5);
define('NOTIFICATION_MESSAGE_NOT_SEND', 6);
define('NOTIFICATION_CLIENT_CONNECTED', 7);
define('NOTIFICATION_CLIENT_DISCONNECTED', 8);
define('NOTIFICATION_MESSAGE_RECEIVED', 9);
define('NOTIFICATION_MESSAGE_END', 10);
class SocketClass
{
private $notifications;//array of registred functions to handle notifications
private $client_sockets;//only used with multithreaded enabled
private $client_socket;//only used with multithreaded disabled
private $multithreaded;//multithreaded enabled/disabled?
private $socket;//server socket
function __construct($multithreaded = true)
{
$this->multithreaded = $multithreaded;
$this->client_sockets = array();
$this->notifications = array();
}
/* NOTIFICATIONS */
//attention: this function overwrites the existing function!
function RegisterNotificationHandler($notification, $function)
{
$this->notifications[$notification] = $function;
}
//this function deletes the notification handler
function UnRegisterNotificationHandler($notification)
{
unset($this->notifications[$notification]);
}
function Notify($notification, $parameter = null, $parameter2 = null, $parameter3 = null)
{
if (isset($this->notifications[$notification]) && is_callable($this->notifications[$notification]))
{
//Attention: call_user_func cannot pass parameters by reference, since itself copies the arguments
call_user_func($this->notifications[$notification], $parameter, $parameter2, $parameter3);
}
}
/* SERVER */
function CreateServer($address, $port)
{
//create a streaming socket, of type TCP/IP
if (($this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0)
{
$this->Notify(NOTIFICATION_SERVER_START_FAILED, socket_last_error($this->socket), socket_strerror($this->socket));
return false;
}
//set the option to reuse the port
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
//Todo: TimeOut
//socket_set_option($socket,SOL_SOCKET, SO_RCVTIMEO, array('sec'=>0, 'usec'=>100));
//"bind" the socket to the address to "localhost", on port $port
//so this means that all connections on this port are now our resposibility to send/recv data, disconnect, etc..
if (($result = socket_bind($this->socket, $address, $port)) < 0)
{
$this->Notify(NOTIFICATION_SERVER_START_FAILED, socket_last_error($result), socket_strerror($result));
return false;
}
//start listen for connections
if (($result = socket_listen($this->socket)) < 0)
{
$this->Notify(NOTIFICATION_SERVER_START_FAILED, socket_last_error($result), socket_strerror($result));
return false;
}
//create a list of all the clients that will be connected to us..
//add the listening socket to this list
if ($this->multithreaded)
$this->client_sockets[] = $this->socket;
unset($result);
$this->Notify(NOTIFICATION_SERVER_START_SUCCEEDED);
return true;
}
function ShutdownServer()
{
if ($this->multithreaded && is_resource($this->client_socket))
if (($result = socket_close($this->client_socket)) < 0)
{
$this->Notify(NOTIFICATION_SERVER_SHUTDOWN_FAILED, socket_last_error($result), socket_strerror($result));
return false;
}
//close the listening socket
if (($result = socket_close($this->socket)) < 0)
{
$this->Notify(NOTIFICATION_SERVER_SHUTDOWN_FAILED, socket_last_error($result), socket_strerror($result));
return false;
}
unset($result);
$this->Notify(NOTIFICATION_SERVER_SHUTDOWN_SUCCEEDED);
return true;
}
public function __call($method, $arguments)
{
//PHP does not support overloaded functions
// => check wether multithreaded is enabled or disabled and call the given function ($method)
// with the determined arguments
//Function: SendMessage
//Arguments: (Multithreaded) $client, $text
// (Singlethreaded) $text
if ($method == 'SendMessage')
{
if ($this->multithreaded)
{
$client = $arguments[0];
$string = $arguments[1];
if (($client < 1) || !isset($this->client_sockets[$client]))
return false;
$client_socket = $this->client_sockets[$client];
if (socket_write($client_socket, $string) === false)
{
$this->Notify(NOTIFICATION_MESSAGE_NOT_SEND, $client_socket, socket_last_error($this->socket), socket_strerror($this->socket));
return false;
}
else
{
$this->Notify(NOTIFICATION_MESSAGE_SENT, $client);
return true;
}
}
else
{
$string = $arguments[0];
if (socket_write($this->client_socket, $string) === false)
{
$this->Notify(NOTIFICATION_MESSAGE_NOT_SEND, socket_last_error($this->socket), socket_strerror($this->socket));
return false;
}
else
{
$this->Notify(NOTIFICATION_MESSAGE_SENT);
return true;
}
}
}
//Function: GetClientIP
//Arguments: (Multithreaded) $client
// (Singlethreaded) n/a
elseif ($method == 'GetClientIP')
{
if ($this->multithreaded)
{
$client = $arguments[0];
if (($client < 1) || !isset($this->client_sockets[$client]))
return false;
socket_getpeername($this->client_sockets[$client], $ip);
return $ip;
}
else
{
socket_getpeername($this->client_socket, $ip);
return $ip;
}
}
//any other function does not seperate their parameters between having multithreaded enabled or having it disabled
//call these functions with call_user_func_array
//else
// return call_user_func_array($method, $arguments);
}
public function ClientConnected()
{
$this->client_socket = socket_accept($this->socket);
}
public function HandleActions($binary = false, $buffer_size = 2048)
{
//Do not use PHP_NORMAL_READ. It returns false on no-data AND on
//connection-closed, so there is no way to detect a closed connection.
//PHP_NORMAL_READ does not pass through \r\n, etc.
//Use PHP_BINARY_READ instead of PHP_NORMAL_READ. It returns an empty string
//on no-data and false on connection-closed. PHP_BINARY_READ passes through \r\n, etc.
if (!$this->multithreaded)
{
while (true)
{
if ($binary)
$buf = @socket_read($this->client_socket, $buffer_size, PHP_BINARY_READ);
else
$buf = @socket_read($this->client_socket, $buffer_size);
if (is_string($buf) && (strlen($buf) > 0))
{
$this->Notify(NOTIFICATION_MESSAGE_RECEIVED, $buf);
}
elseif ($buf === '')
{
$this->Notify(NOTIFICATION_MESSAGE_END);
break;
}
else
{
$this->Notify(NOTIFICATION_CLIENT_DISCONNECTED);
break;
}
}
}
else
{
$lastbuf = array();
$lastaction = array();
while (true)
{
//create a copy, so $this->client_sockets doesn't get modified by socket_select()
$read = $this->client_sockets;
//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
if (socket_select($read, $write = null, $except = null, 0) < 1)
continue;
//check if there is a client trying to connect
if (in_array($this->socket, $read))
{
//accept the client, and add him to the $this->client_sockets array
$this->client_sockets[] = $new_socket = socket_accept($this->socket);
$this->Notify(NOTIFICATION_CLIENT_CONNECTED, count($this->client_sockets) - 1);
//remove the listening socket from the clients-with-data array
$key = array_search($this->socket, $read);
unset($read[$key]);
}
//loop through all the clients that have data to read from
foreach ($read as $read_socket)
{
//corresponding key to the current socket
$key = array_search($read_socket, $this->client_sockets);
if ($binary)
$buf = @socket_read($read_socket, $buffer_size, PHP_BINARY_READ);
else
$buf = @socket_read($read_socket, $buffer_size);
if (!isset($lastbuf[$key]))
$lastbuf[$key] = null;
if (!isset($lastaction[$key]))
$lastaction[$key] = null;
if ((($buf === '') && ($lastbuf[$key] === '')) || ($buf === false)) //client disconnected
{
$this->Notify(NOTIFICATION_CLIENT_DISCONNECTED, $key);
$lastaction[$key] = NOTIFICATION_CLIENT_DISCONNECTED;
unset($this->client_sockets[$key]);
}
elseif ($buf === '') //the end of the message has been reached
{
//Do not send a notification if no message has been received. This happens e. g. if a
//user logs in and disconnects whithout having leaved a message
if ($lastaction[$key] == NOTIFICATION_MESSAGE_RECEIVED)
{
$this->Notify(NOTIFICATION_MESSAGE_END, $key);
$lastaction[$key] = NOTIFICATION_MESSAGE_END;
}
}
else
{
$this->Notify(NOTIFICATION_MESSAGE_RECEIVED, $key, $buf);
$lastaction[$key] = NOTIFICATION_MESSAGE_RECEIVED;
}
$lastbuf[$key] = $buf;
}
}
unset($lastbuf);
unset($lastbuf);
}
}
}
?>