POST PART II
main socket server code (here)
#!/usr/bin/php -q
<?php
function flashmog_server_error($err_mesg, $is_fatal, $filename, $line_no) {
$output = "FlashMOG server error on line " . $line_no . " of file " . $filename . ":\n" . $err_mesg . "\n";
if ($is_fatal) {
die($err_mesg . "Execution_halted.\n");
} else {
echo($err_mesg);
}
} // flashmog_server_error()
function call_client_method($socket_id, $service_name, $method_name, $args) {
$rpc_array = array($service_name, $method_name, $args);
$output = serialize($rpc_array) . chr(0);
socket_write($socket_id, $output)
or die('socket_write failed!');
} // call_client_method()
set_time_limit (0);
// config this server script
define('FM_HOST', 'jaith.net');
define('FM_PORT', 1234);
define('FM_FS_ROOT', '/home/jaith/public/flashmog/');
define('FM_MAX_CLIENTS', 10); // the maximum number of clients permitted
define('FM_SOCKET_BACKLOG', 5); // the maximum number of connections queued
// for processing by socket_listen()
define('FM_SOCKET_SELECT_TIMEOUT', NULL); // the amount of time socket_select() call
// will block before returning without any
// sockets having changed. NULL means it will
// block indefinitely
define('FM_SOCKET_READ_LENGTH', 1024); // the number of bytes to be read from a socket
define('FM_COMMAND_EXIT', 'exit'); // if the only data coming over the socket is this
// string, then close the socket connection to the client
define('FM_SERIALIZER_METHOD', 'PHP');
switch (FM_SERIALIZER_METHOD) {
case 'PHP':
require_once('classes/serialize_php.php');
break;
default:
flashmog_server_error("Unrecognized serialization method: " . FM_SERIALIZE_METHOD, true, __FILE__, __LINE__);
}
require_once('classes/flashmog_service.php');
// require_once all classes in the services directory
$service_class_dir = FM_FS_ROOT . 'services/';
echo "Including service classes...\n";
if ($handle = opendir($service_class_dir)) {
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != ".." && !preg_match("#.*\.LCK#", $file)) { // exclude Dreamweaver LCK files
echo ' ' . $file . "\n";
require_once($service_class_dir . $file);
}
}
closedir($handle);
} else {
flashmog_server_error("Could not read service class directory! ", true, __FILE__, __LINE__);
}
// Create a TCP socket
$main_listening_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$main_listening_socket) {
$socket_error_code = socket_last_error();
flashmog_server_error("socket_create() failed: " . socket_strerror($socket_error_code), true, __FILE__, __LINE__);
}
if (!socket_set_option($main_listening_socket, SOL_SOCKET, SO_REUSEADDR, 1)) {
$socket_error_code = socket_last_error($main_listening_socket);
flashmog_server_error("socket_set_option SO_REUSEADDR failed: " . socket_strerror($socket_error_code), true, __FILE__, __LINE__);
}
// set socket to non-blocking
if (!socket_set_nonblock($main_listening_socket)) {
$socket_error_code = socket_last_error($main_listening_socket);
flashmog_server_error("socket_set_nonblock() failed: " . socket_strerror($socket_error_code), true, __FILE__, __LINE__);
}
// Bind the socket to an address/port
if (!socket_bind($main_listening_socket, FM_HOST, FM_PORT)) {
$socket_error_code = socket_last_error($main_listening_socket);
flashmog_server_error("socket_bind() failed: " . socket_strerror($socket_error_code), true, __FILE__, __LINE__);
}
// Start listening for connections
if (!socket_listen($main_listening_socket, FM_SOCKET_BACKLOG)) {
$socket_error_code = socket_last_error($main_listening_socket);
flashmog_server_error("socket_listen() failed: " . socket_strerror($socket_error_code), true, __FILE__, __LINE__);
}
$sockets_to_write = array(); // dummy var for passing to socket_select
$sockets_to_except = array(); // dummy var for passing to socket_select
// Array that will hold client information
$clients = array();
// MAIN LOOP
while (true) {
// specify which sockets need listening...should include
// all of our connected clients
$sockets_to_read = array();
$sockets_to_read[0] = $main_listening_socket;
foreach ($clients as $client) {
if ($client['socket_id'] != null)
$sockets_to_read[] = $client['socket_id'];
}
// Set up a blocking call to socket_select()...this call alters
// the $sockets_to_xxxx arrays so that they indicate which sockets
// need attention
$num_changed_sockets = socket_select($sockets_to_read, $sockets_to_write, $sockets_to_except, FM_SOCKET_SELECT_TIMEOUT);
// for some reason, I thought it was a good idea to accept any new clients first
// if a new connection is being made add it to the client array
if (in_array($main_listening_socket, $sockets_to_read)) {
if (sizeof($clients) >= FM_MAX_CLIENTS) {
echo "rejecting client - max clients already reached\n";
} else {
$new_client = array();
$new_client['socket_id'] = socket_accept($main_listening_socket);
$s_host = NULL;
$s_port = NULL;
socket_getpeername($new_client['socket_id'], $s_host, $s_port);
echo "new client accepted, host=" . $s_host . ", port=" . $s_port . "\n";
$new_client['host'] = $s_host;
$new_client['port'] = $s_port;
$clients[] = $new_client;
}
// if there was only one socket to deal with, we have dealt with it
if ($num_changed_sockets == 1) {
continue; // start main loop again
}
} // if socket changed is $main_listening_socket
// loop through all our clients and deal with those whose socket_ids
// show up in the $sockets_to_read array
foreach($clients as $client_key => $client) {
if (in_array($client['socket_id'] , $sockets_to_read)) {
$incoming_socket_data = socket_read($client['socket_id'], FM_SOCKET_READ_LENGTH, PHP_BINARY_READ);
if ($incoming_socket_data === false) {
// the PHP docs say this means the socket was closed remotely
// :TODO: should this be an error instead?
echo "FALSE received on socket_read(), closing socket id #" . $client['socket_id'] . "\n";
if (is_resource($client['socket_id'])) {
socket_close($client['socket_id']);
}
unset($clients[$client_key]);
} elseif ($incoming_socket_data === '') {
// Zero length string meaning disconnected
echo "zero-length string received on socket_read(), closing socket id #" . $client['socket_id'] . "\n";
if (is_resource($client['socket_id'])) {
socket_close($client['socket_id']);
}
unset($clients[$client_key]);
} else {
// socket is neither empty nor false....should be real data
// HANDLE DATA HERE
$incoming_socket_data = trim($incoming_socket_data);
if ($incoming_socket_data == FM_COMMAND_EXIT) {
// requested disconnect
echo "client " . $client['socket_id'] . "entered 'exit' request, closing\n";
if (is_resource($client['socket_id'])) {
socket_close($client['socket_id']);
}
} elseif ($incoming_socket_data) {
// assume the incoming data is a serialized array of 3 elements
$rpc_array = Serializer::unserialize($incoming_socket_data);
if (!$rpc_array) {
flashmog_server_error("Could not unserialize incoming RPC from socket #" . $client['socket_id'] . "\n" . $incoming_socket_data, false, __FILE__, __LINE__);
} elseif (!is_array($rpc_array)) {
flashmog_server_error("Incoming RPC is not an array from socket #" . $client['socket_id'] . "\n" . print_r($rpc_array, true), false, __FILE__, __LINE__);
} elseif (sizeof($rpc_array) != 3) {
flashmog_server_error("Incoming RPC has wrong member count from socket #" . $client['socket_id'] . "\n" . print_r($rpc_array, true), false, __FILE__, __LINE__);
} else {
// :TODO: we might want to ban certain classes and methods here for security
list($service_class_name, $method_name, $rpc_parameters) = $rpc_array;
if (class_exists($service_class_name)) {
$service_object = new $service_class_name();
$service_object->calling_socket_id = $client['socket_id'];
if (method_exists($service_object, $method_name)) {
$svc_method_ary = array($service_object, $method_name);
// :TODO: put try/catch block here to handle exceptions
ksort($rpc_parameters); // sadly, due to a quirk of the sephiroth serialize in flash
// we must sort the parameters to keep them from being reversed
call_user_func_array($svc_method_ary, $rpc_parameters);
// some cleanup
unset($service_object);
} else {
flashmog_server_error("Incoming RPC class " . $service_class_name . " doesn't have method " . $method_name . " from socket #" . $client['socket_id'], false, __FILE__, __LINE__);
} // if method exists
} else {
flashmog_server_error("Incoming RPC class doesn't exist from socket #" . $client['socket_id'] . "\n" . $service_class_name, false, __FILE__, __LINE__);
} // if service_class exists
} // if rpc_array is a propery rpc request array
} // if socket input...
} // if socket input is empty string or false
} // if socket in read array
} // foreach client socket
} // end while MAIN LOOP
if (is_resource($main_listening_socket)) {
socket_close($main_listening_socket);
}
?>