在代码审查下方,如果可能有用的话。
我还创建了一个专用类 (octave_stream),以提供一个接口,例如,允许 html 页面按需启动专用 octave 进程(使用 SSE 通信),然后将命令作为 ajax 请求一一发送。每当用户离开页面或在未使用时间之后,sw 将关闭 octave 进程。
下面是基于以下4个文件的完整代码:octave.php、octave_sse.php、octave_com.php。
octave.php
<?php
class octave {
private $pipes = null;
private $process = null;
private $add_semicolon = false;
private $debug = false;
private $print = false;
function __destruct() {
$this->close_octave();
}
public function close_octave(){
if ($this->pipes !== null){
fclose($this->pipes[0]);
fclose($this->pipes[1]);
$return_value = proc_close($this->process);
if($this->debug){ echo "command returned $return_value<br>";}
return $return_value;
}
return false;
}
public function open_octave(){
$dir = sys_get_temp_dir();
$error_file_name = tempnam($dir, "npt");
$descriptorspec = array(
0 => array("pipe", "rb"), // stdin is a pipe that the child will read from
1 => array("pipe", "wb"), // stdout is a pipe that the child will write to
2 => array("file", $error_file_name,"a") // stderr:: you HAVE TO specify a file
);
$octave = 'C:\Octave\Octave-4.4.0\bin\octave-cli-4.4.0 ';
$cmd = $octave . ' --no-gui -i -q';
$pipes = array();
$this->process = proc_open($cmd, $descriptorspec, $pipes);
if (!is_resource($this->process)) {
echo "PROCESS CREATION ERROR CMD: $cmd";
return false;
}
if (count($pipes) == 0){
echo 'pipes: '; var_dump($pipes); echo '<br>';
echo "PROCESS CREATION ERROR CMD: $cmd";
return false;
}
//stream_set_blocking($pipes[1],false); // doesn't work on STDIN / OUT
$this->pipes = $pipes;
$cmd_id = 0;
$this->read($cmd_id);
return true;
} //end open_octave
private function raw_read(){
$str = fread($this->pipes[1],8192);
if($str === false){
echo '<br>'. __FUNCTION__ . ' fgets ERROR for CMD: ' . htmlentities($cmd);
return false;
}
//echo "READ: $str<br>";
return $str;
} //end read
private function read(&$cmd_num){
$result = '';
$string = $this->raw_read();
if($this->debug){echo '<br>' . __FUNCTION__ . ' RAW_READ1: ' . $string; }
if($string[0] == '>'){
//special case, multiple line statement
if($this->debug){echo '<br>' . __FUNCTION__ . ' multiple line statement.' ; }
$cmd_num = -1;
return '';
}
$tok = strtok($string, "octave:");
while (($tok === false) || ($tok == $string)) {
//the response do not conatin the promt; therefore, other rows are coming, read them as well and concatenate to the current response
$result .= $string;
usleep(100);
$string = $this->raw_read();
if($this->debug){echo '<br>' . __FUNCTION__ . ' RAW_READ NEXT: ' . $string; }
$tok = strtok($string, "octave:");
}
$cmd_num = -1;
//in this case string include the promt; nothing more to be read. tok is the string without the "ocatve:" but you still have the 'num>' to be removed.
$allTheRest = strtok( '' );
if(($allTheRest === false) || ($allTheRest == '')){
//tok has the promt rest
list($cmd_num) = sscanf($tok,'%u>');
}else{
$result .= $tok;
list($cmd_num) = sscanf($allTheRest,'ctave:%u>');
}
if($this->debug){echo '<br>' . __FUNCTION__ . " RAW_READ TOK: '$tok' STRING: '$string' RESULT: '$result' allTheRest: '$allTheRest' cmd_num: $cmd_num ## "; }
return $result;
}
//return the command answer removing the promt. Therefore, in case no retrun message command an empty string is given
public function octave_cmd($cmd,&$cmd_num){
if($this->add_semicolon){
$last_chr = strlen($cmd) - 1;
if(substr($cmd, $last_chr, 1) != ';'){
$cmd = $cmd . ';'; // adding ; at the input command required by octave
}
}
//echo "$cmd<br>";exit(0);
$cmd = $cmd . PHP_EOL ;// "\r\n"
if(fwrite($this->pipes[0],$cmd) === false){
echo '<br>'. __FUNCTION__ . ' fwrite ERROR for CMD: ' . htmlentities($cmd);
return false;
}
//usleep(100); // needed when more output row are given ... without this you read before the progam write the next row ... and you lost it ... a nice solution anyway should be to read again unil you find the promt that is always the latest given.
$ret = $this->read($cmd_num);
if($this->print){
echo "<br>COMMAND # $cmd_num: " .htmlentities($cmd) . (($ret != '')?(' RESULT: ' . nl2br(htmlentities($ret))):'') . ' END<br>';
}
return $ret;
}
public function print_cmd($cmd){
$cmd_id = 0;
$ret = $this->octave_cmd($cmd,$cmd_id);
echo "<br>COMMAND # $cmd_id: " .htmlentities($cmd) . (($ret != '')?(' RESULT: ' . nl2br(htmlentities($ret))):'') . ' END<br>';
}
}
class octave_stream {
private $address = 'tcp://127.0.0.1';
private $port = 5454;
private $errno = 0;
private $errstr = '';
private $server_istance = false;
private $sse = false;
/*This function start octave process, open a tcp connection and waits for incoming commands.
(to send the commands use the func. send_cmd)
Each command received is forwared to octave and the ocatve answer is forwared to who sent the command.
To stop the server a special command has to be sent. The caller has to use the stop_server()
*/
public function create_server($accept_max_retray = 30){
$this->server_istance = true; // first command to be done
$available_port = $this->find_usable_listening_port($this->port);
if($available_port === false){
$this->server_log('NOT FOUND AN AVAILABLE PORT TO START THE LISTENING SERVER');
return false;
}else{
$this->port = $available_port;
}
$oct_obj = new octave();
if(!$oct_obj->open_octave()){
return false;
}
$this->server_log('OCTAVE PROCESS STARTED');
$cmd_id = 0;
$server_socket_address = 'tcp://127.0.0.1:' . $this->port;
$socket = stream_socket_server($server_socket_address, $errno, $errstr);
if (!$socket) {
$this->errstr = "$errstr ($errno)<br />\n";
$this->errno = $errno;
$oct_obj->close_octave();
$this->server_log("<b>LISTENING SOCKET CREATION ERROR: $this->errstr</b>");
return false;
}
$exit = false;
$this->server_log("LISTENING SOCKET CREATED: $server_socket_address DEFAULT SOCKET TIMEOUT:" . ini_get("default_socket_timeout"));
$this->server_log($server_socket_address,'server_ready'); // dedicated event type sent, html page has a dedicated event listener on it
$accept_retray = -1;
$conn_id = 0;
do{
$accept_retray += 1;
while ($conn = stream_socket_accept($socket)) {
$this->server_log("LISTENING SOCKET CONNECTION ACCEPTED: $conn_id");
$accept_retray = -1;
//cmd: received octave command request to be sent to octave
$cmd = '';
$cmd = fread($conn,8192);
if($cmd === false){
$this->server_log( __FUNCTION__ . "[$conn_id] fread ERROR for CMD: " . htmlentities($cmd));
return false;
}
$this->server_log("[$conn_id] RECEIVED RAW COMMAND: $cmd");
if(strpos($cmd, '@exit@') !== false){
//special command EXIT
$this->server_log("[$conn_id] RECEIVED EXIT COMMAND.");
fwrite($conn,self::prefix($conn_id) . 'RECEIVED EXIT COMMAND.'); // you need always to send an answer to the calling (it is waiting on read it
$exit = true;
}else if (strpos($cmd, '@loopback:') !== false) {
//special command LOOPBACK
$this->server_log("[$conn_id] RECEIVED LOOPBACK COMMAND: $cmd");
fwrite($conn,self::prefix($conn_id) . $cmd); // you need always to send an answer to the calling (it is waiting on read it
} else {
//OCATVE COMMAND
$this->server_log("[$conn_id] RECEIVED COMMAND FOR OCTAVE: $cmd");
$cmd_id = 0;
$ret = $oct_obj->octave_cmd($cmd,$cmd_id);
$this->server_log("[$conn_id] COMMAND # $cmd_id: " .htmlentities($cmd) . (($ret != '')?(' RESULT: ' . nl2br(htmlentities($ret))):'') . ' END<br>');
fwrite($conn,self::prefix($conn_id) . 'RESULT:'. $ret);
$this->server_log("[$conn_id] SENT ANSWER: RESULT:". $ret);
}
fclose($conn);
$this->server_log("[$conn_id] CLOSE CONNECTION");
$conn_id++;
if($exit) break;
}
if($exit) break;
//note, calling the server_log func here you are testing the socket between this script and the html pages. If the sse connection is no more working, the PHP engine will close this script.
$this->server_log("LISTENING SOCKET CONNECTION TIMEOUT RETRAY: $accept_retray MAX_RETRAY: $accept_max_retray");
}while($accept_retray < $accept_max_retray);
$this->server_log($server_socket_address,'server_stopped'); // dedicated event type sent
fclose($socket);
$this->server_log('SOCKET CLOSED.');
unset($oct_obj);
$this->server_log('OCTAVE PROCESS STOPPED.');
return true;
} // end start_server
private static function prefix($conn_id){
return "[$conn_id] [" . date('n/j/Y g:i:s a') .'] ';
}
//return the ocatve command answer to be encoded e.g. json to be delivered to the http page or false
public function send_cmd($cmd){
if($this->server_istance){
$this->errstr = '<br>'. __FUNCTION__ . ' SERVER ISTANCE.';
return false;
}
$ret = '';
$fp = stream_socket_client($this->address . ':' .$this->port , $errno, $errstr, 30);
if ($fp === false) {
$this->errstr = "$errstr ($errno)<br />\n";
$this->errno = $errno;
return false;
} else {
//fwrite($fp, "GET / HTTP/1.0\r\nHost: www.example.com\r\nAccept: */*\r\n\r\n");
fwrite($fp, $cmd);
while (!feof($fp)) {
$ret .= fgets($fp, 1024);
}
fclose($fp);
}
return $ret;
} // end send_cmd
//return false or the server answer
public function stop_server(){
if($this->server_istance){
$this->errstr = '<br>'. __FUNCTION__ . ' SERVER ISTANCE.';
$this->errno = 0;
return false;
}
return $this->send_cmd('@exit@');
} //
//return false or the server answer
public function loopback_msg($msg){
if($this->server_istance){
$this->errstr = '<br>'. __FUNCTION__ . ' SERVER ISTANCE.';
$this->errno = 0;
return false;
}
return $this->send_cmd('@loopback:' . $msg);
}
public function get_errstr(&$errno){
$ret = $this->errstr;
$this->errstr = '';
$errno = $this->errno;
$this->errno = 0;
return $ret;
}
//$ip_name: IP or dns name
public function set_address_connection($ip_name,$port){
$this->address = 'tcp://' . $ip_name ;
$this->port = $port;
}
//str can be encoded as json if you need to transfert more data field.
//see: https://www.html5rocks.com/en/tutorials/eventsource/basics/
private function server_log($str,$event_name = ''){
$msg = '<br>[' . date('n/j/Y g:i:s a') .'] '. $str ;
if($this->sse){
if($event_name != ''){
echo "event: $event_name" . PHP_EOL;
echo "data: $str" . PHP_EOL;
}else{
echo "data: $msg" . PHP_EOL;
}
echo PHP_EOL;
}else{
echo $msg;
}
ob_flush();
flush();
}
public function set_sse($sse){
$this->sse = $sse;
}
private function check_server_running_on_port($port,&$ret_errstr,&$errno){
if(!$this->server_istance){
$ret_errstr = '<br>'. __FUNCTION__ . ' REQUIRED SERVER ISTANCE.';
$errno = 0;
return false;
}
$ret_errstr ='';
$errno = 0;
$msg = __FUNCTION__ . ' PORT CHECK: ' . $port;
$cmd = '@loopback:' . $msg; // you cannot change it, is the defined loopback command
$fp = stream_socket_client($this->address . ':' .$port , $errno, $errstr, 30);
if ($fp === false) {
$ret_errstr = "$errstr ($errno)<br />\n";
return false;
}
fwrite($fp, $cmd);
while (!feof($fp)) {
$ret .= fgets($fp, 1024);
}
fclose($fp);
return true;
}
private function find_usable_listening_port($start_port){
if(!$this->server_istance){
$this->errstr = '<br>'. __FUNCTION__ . ' REQUIRED SERVER ISTANCE.';
$this->errno = 0;
return false;
}
$ret_errstr ='';
$errno = 0;
$max_port = $start_port +10;
$port = $start_port;
do{
//ret = true server running, so I need to find another unused port
$ret = $this->check_server_running_on_port($port,$ret_errstr,$errno);
if($ret){
$this->server_log("CHECK USABLE LISTENING PORT: PORT=$port ALREADY USED.");
}else{
$this->server_log("CHECK USABLE LISTENING PORT: PORT=$port NOT USED. GIVEN ERROR: $ret_errstr" );
return $port;
}
$port++;
if($port > $max_port) return false;
}while($ret);
return false;
}
} // end class
?>
octave_sse.php
<?php
/* this page should be called by the web page as sse
this page calling the create_server func start the octave process and the communication server as well.
The function return only when the exit command is received over the server channel created.
Then the page has to send a polling to the web page to test the presence of the page itself.
*/
function sendMsg($sse,$msg) {
if($sse){
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
}else{
echo $msg;
}
ob_flush();
flush();
}
$sse = false;
if(isset($_GET['sse'])){
if($_GET['sse'] > 0){
$sse = true;
}
}
if($sse){
header('Content-Type: text/event-stream');
}else{
header( 'Content-type: text/html; charset=utf-8' );
}
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
require 'octave.php';
set_time_limit(4000);
$oct_server_obj = new octave_stream();
if($sse){
$oct_server_obj->set_sse(true);
}
sendMsg($sse,'<br>[' . date('n/j/Y g:i:s a') .'] CALLING create_server.<br>');
$ret = $oct_server_obj->create_server();
sendMsg($sse,'<br>[' . date('n/j/Y g:i:s a') .'] create_server EXIT. return: '. (($ret)?'TRUE':'FALSE').'<br>');
//you are here only at the end when the web page send the exit command to the created server
do{
sleep(2);
sendMsg($sse,'<br>[' . date('n/j/Y g:i:s a') .'] create_server PING<br>');
}while(true);
?>
octave_com.php
<?php
/*
This page is called using a json post request from web page.
Each request has a octave command. special param is to terminate the server.
*/
/** Error reporting: this is not used since you are using myErrorHandler */
if($_SERVER['SERVER_NAME'] == "localhost"){ // my PC
error_reporting(E_ALL);
}else{
error_reporting(E_ERROR | E_PARSE);
}
//http://php.net/manual/it/errorfunc.configuration.php#ini.display-errors
/* The errors are redirect over the stderr and not over the stdout.
In this way, you have to properly send the error using json without using the set_error_handler.
ini_set('display_errors','Off');
//ini_set('display_errors','On');
*/
function myErrorHandler ($errno, $errstr, $errfile, $errline) {
if (0 === error_reporting()) { return false;}
$errorType = array (
E_ERROR => 'ERROR',
E_WARNING => 'WARNING',
E_PARSE => 'PARSING ERROR',
E_NOTICE => 'NOTICE',
E_CORE_ERROR => 'CORE ERROR',
E_CORE_WARNING => 'CORE WARNING',
E_COMPILE_ERROR => 'COMPILE ERROR',
E_COMPILE_WARNING => 'COMPILE WARNING',
E_USER_ERROR => 'USER ERROR',
E_USER_WARNING => 'USER WARNING',
E_USER_NOTICE => 'USER NOTICE',
E_STRICT => 'STRICT NOTICE',
E_RECOVERABLE_ERROR => 'RECOVERABLE ERROR');
// create error message
if (array_key_exists($errno, $errorType)) {
$err = $errorType[$errno];
} else {
$err = 'CAUGHT EXCEPTION';
}
if($_SERVER['SERVER_NAME'] != "localhost"){ // not my PC
if (!($errorType == E_ERROR ) || ($errorType == E_PARSE ))
return;
}
$str_err = "$err($errno) <br> in line: " .$errline. " of file: " .$errfile . " <br> PHP: " . PHP_VERSION . " (". PHP_OS .")<br> $errstr";
terminate($str_err);
} // end myErrorHandler
// configura il gestore dell'errore definito dall'utente
$old_error_handler = set_error_handler("myErrorHandler");
// functions
function terminate($str_err, $txt=false){
if($txt){
die($str_err);
}
header("Content-Type: application/json;charset=utf-8");
echo json_encode(array("result"=>"false","error"=>$str_err));
exit(0);
} // end terminate
require 'octave.php';
if(isset($_POST['send_loopback'])){
if(!isset($_POST['message'])){
terminate("message not present!");
}else{
$message = $_POST['message'];
}
$address = '';
$ip='';
$port = 0;
if(isset($_POST['address'])){
$address = $_POST['address'];
$pos = strrpos($address,':');
$ip = substr($address,6,$pos-6);
$port = substr($address,$pos+1);
}
$oct_server_obj = new octave_stream();
//it is supposed to have the server already running ready to manage the sent messages here below
//echo '<br>[' . date('n/j/Y g:i:s a') .'] SEND THE LOOPBACK MESSAGE.<br>';
if($ip != '' and is_numeric($port)){
$oct_server_obj->set_address_connection($ip,$port);
}
$msg = $oct_server_obj->loopback_msg($message);
if($msg === false){
$errno = 0;
terminate($oct_server_obj->get_errstr($errno));
}
echo json_encode(array('result' => 'true', 'error' => '', 'loopback_msg' => $msg)) ;
exit(0);
} // end send_loopback
if(isset($_POST['send_octave_msg'])){
if(!isset($_POST['message'])){
terminate("message not present!");
}else{
$message = $_POST['message'];
}
$address = '';
$ip='';
$port = 0;
if(isset($_POST['address'])){
$address = $_POST['address'];
$pos = strrpos($address,':');
$ip = substr($address,6,$pos-6);
$port = substr($address,$pos+1);
}
$oct_server_obj = new octave_stream();
//it is supposed to have the server already running ready to manage the sent messages here below
//echo '<br>[' . date('n/j/Y g:i:s a') .'] SEND THE LOOPBACK MESSAGE.<br>';
if($ip != '' and is_numeric($port)){
$oct_server_obj->set_address_connection($ip,$port);
}
$msg = $oct_server_obj->send_cmd($message);
if($msg === false){
$errno = 0;
terminate($oct_server_obj->get_errstr($errno));
}
$pos = strpos($msg,'RESULT:');
$ret = trim(substr($msg,$pos+strlen('RESULT:')));
$ret = nl2br(htmlentities($ret));
echo json_encode(array('result' => 'true', 'error' => '', 'msg' => $msg, 'return' => $ret)) ;
exit(0);
} // end send_octave_msg
if(isset($_GET['send_exit_msg'])){
$address = '';
$ip='';
$port = 0;
if(isset($_GET['address'])){
$address = $_GET['address'];
$pos = strrpos($address,':');
$ip = substr($address,6,$pos-6);
$port = substr($address,$pos+1);
}
$oct_server_obj = new octave_stream();
//it is supposed to have the server already running ready to manage the sent messages here below
//echo '<br>[' . date('n/j/Y g:i:s a') .'] SEND THE LOOPBACK MESSAGE.<br>' . "ADDRESS: $address IP: $ip PORT: $port<br>" ;
if($ip != '' and is_numeric($port)){
$oct_server_obj->set_address_connection($ip,$port);
}
$msg = $oct_server_obj->stop_server();
if($msg === false){
$errno = 0;
terminate($oct_server_obj->get_errstr($errno));
}
echo json_encode(array('result' => 'true', 'error' => '', 'return' => $msg)) ;
exit(0);
} // end send_exit_msg
?>