martedì 23 dicembre 2014

[PHP bash Centos] create a server with a PHP script and daemonize it

I started from the need to receive JSON data from some devices, parse them and put in MySQL. Nothing better than a PHP script to perform this job, except that you need to manually launch it via bash, your ssh access will timeout, you could also check it via crontab with CPU waste, etc etc.

So I just developed my server listening on a port, made it able to receive multiple client connections and daemonized it: now I can start, stop, restart, ps au -> kill it, cat a logfile, have it ready on system reboot and everything I would expect from a regularly compiled application written in C++.

BUT... I can modify it whenever I like without recompiling it, it natively talks with its companion website, etc etc.

I tried mpdaemon and several other solutions... I already tested them all for you: at the end, nothing better than the easy way.

Tested with CentOS but you can easily apply it to Ubuntu.

PHP SERVER:

#!/usr/bin/php

<?

####### PHP ERROR HANDLING #######
if (function_exists('ini_set')) {
ini_set('display_errors', '1');
ini_set('error_reporting', '7');
ini_set('max_execution_time',0);// INFINITE
ini_set("memory_limit", "300M");
} else
error_reporting(E_ALL & ~E_NOTICE);
/*----------------------------------*/

####### INITIALIZE VARIABLES #######
date_default_timezone_set("Europe/Rome");
$client_socks = array(); //WILL CONTAIN ALL CLIENT SOCKETS
$client_data = array(); //WILL CONTAIN CLIENTS IP:PORT just for debug & compliance
$port = 4000;
$log = '/var/log/myDAEMON.log';


####### INCLUDE MYSQL AND OTHER CONFIGS #######
include("config.php");
/*----------------------------------*/

####### MYSQL CONNECTION #######
mysql_connect($dbhost, $dbuser, $dbpass) or die(mysql_error());
mysql_select_db($dbname) or die(mysql_error());
mysql_query("SET CHARACTER SET utf8");
/*----------------------------------*/



//init socket
$socket = stream_socket_server("tcp://0.0.0.0:".$port, $errno, $errorMessage) or die("Could not bind to socket: $errorMessage");

//fork the process to work in a daemonized environment
file_put_contents($log, date("d.m.y H:i",time())." starting up.\n", FILE_APPEND);
$pid = pcntl_fork();
if($pid == -1) {
file_put_contents($log, "Error: could not daemonize process.\n", FILE_APPEND);
return 1; //error
} else if($pid) {
return 0; //success
} else {

    ##############################
    //THIS IS WHERE YOU SHOULD PLACE ANY MYSQL CONNECTION
    //since it's after the fork (it will receive a new PID)
    //else you will get crazy debugging the 2006 error "mysql server has gone"
mysql_connect($dbhost, $dbuser, $dbpass) or die(mysql_error());
mysql_select_db($dbname) or die(mysql_error());
mysql_query("SET CHARACTER SET utf8");
    ##############################

    ##############################
    //the main process
    while(true) {

        //prepare readable sockets array
        $read_socks = $client_socks; //will be filled below and then looped
        $read_socks[] = $socket; //server starts here

//select all sockets one by one and apply long timeout
stream_select ( $read_socks, $write, $except, 300000 ) or die('something went wrong while selecting a socket');

//check for new connected clients
if (in_array($socket, $read_socks)) {
$new_client = stream_socket_accept($socket);

if ($new_client) {
//remote client info, ip, port number
$client_info = strtolower( stream_socket_get_name($new_client, true) );
echo "Connection accepted from " . $clientinfo . "\n";

//add it in array client_socks
$client_socks[] = $new_client;
$client_data[] = $client_info;
echo "Now there are total ". count($client_socks) . " clients.\n";
}

//clean read_socks
unset($read_socks[ array_search($socket, $read_socks) ]);
}

//messagges from the clients
foreach($read_socks as $sock) {

//whom client speaking to?
$clientkey = array_search($sock, $client_socks);

//here I read the stream
$data = fread($sock, 128);

if(!$data) {
unset($client_socks[ $clientkey ]);
unset($client_data[ $clientkey ]);
@fclose($sock);
echo "A client disconnected. Now there are total ". count($client_socks) . " clients.\n";
continue;
}

//PARSE DATA AND PUT IN MYSQL

[W H A T E V E R   Y O U   L I K E]

//FILL DB
$query = "INSERT INTO db SET everything you want";
$sql = mysql_query($query) or die(mysql_error());
}
    }
}
?>

If you put some echos, please consider that I did in 2 ways:
  1. append to logfile
  2. echo out
Where you see that I echoed out, consider that this will echo in the shell and it's perfect for live debug as you start the socket. But in a production environment you would append to the logfile instead.

BASH SCRIPT /etc/init.d (copy e.g. crond and you will inherit the right permission, then write over this):

#!/bin/bash
#
# /etc/init.d/myDAEMON
#
# Starts myDAEMON
#
# chkconfig: 345 95 5
# description: Runs the myDAEMON daemon.
# processname: myDAEMON

# Source function library.
. /etc/init.d/functions

#startup values
log=/var/log/myDAEMON.log

#verify that the executable exists
test -x /var/www/html/myDAEMON.php || exit 0RETVAL=0

#
# Set prog, proc and bin variables.
#
prog="myDAEMON"
proc=/var/lock/subsys/myDAEMON
bin=/var/www/html/myDAEMON.php

start() {
# Check if myDAEMON is already running
if [ ! -f $proc ]; then
   echo -n $"Starting $prog: "
   daemon $bin --log=$log
   RETVAL=$?
   [ $RETVAL -eq 0 ] && touch $proc
   echo
fi

return $RETVAL
}

stop() {
echo -n $"Stopping $prog: "
killproc $bin
RETVAL=$?
[ $RETVAL -eq 0 ] && rm -f $proc
echo
        return $RETVAL
}

restart() {
stop
start
}

reload() {
restart
}

status_at() {
  status $bin
}

case "$1" in
start)
start
;;
stop)
stop
;;
reload|restart)
restart
;;
condrestart)
        if [ -f $proc ]; then
            restart
        fi
        ;;
status)
status_at
;;
*)

echo $"Usage: $0 {start|stop|restart|condrestart|status}"
exit 1
esac

exit $?
exit $RETVAL

Now you can type from the shell
service myDAEMON start
service myDAEMON stop
service myDAEMON restart
chkconfig --add myDAEMON

which means that you can start, stop, restart and start on launch, like you usually do for every other binary... ah, WHAT A FUN!!!!