I writing a command and then reading back from a server via sockets in PHP. We have 20 servers that all run a Node JS script which can receive these commands and execute them. The Node JS script will return "ok" which PHP reads back to confirm the command has gone through.
The Node JS script listens on port 9000 and is set to allow half open.
Most of the time this works fine, but when a high volume of commands are sent we get errors back occasionally that say this:
Contents: Message received back from socket was 'Unexpected token {'
Transport endpoint is not connected
The transport endpoint message suggests to me that it has not connected successfully.
I am no expert in sockets so I don't know whether the implementation I have used is "correct". It does work most of the time but I am aware that there are functions like socket_bind and socket_listen that may work better, though I am not sure what they do.
Here is the PHP code that we are using. Any suggestions would be most appreciated.
public function sendDaemonCommand($address, $template_id, $params = array()) {
$hostname = $this->getHostnameFromPrivateIP($address);
$port = 9000;
$command = array('template_id' => $template_id, 'params' => $params);
$command = json_encode($command);
// Create a TCP Stream socket
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
$this->mailError("Command Failed - " . $hostname, "Failed to create socket on " . $address . "\n\n" . socket_strerror(socket_last_error()) . "\n\nCommand:\n\n" . $command . "\n" . $this->functionTraceback());
return false;
}
// Connect to socket
if (socket_connect($sock, $address, $port) === false) {
$this->mailError("Command Failed - " . $hostname, "Failed to connect to socket on " . $address . "\n\n" . socket_strerror(socket_last_error($sock)) . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback());
socket_close($sock);
return false;
}
// Write command to socket
$_command = $command;
$length = strlen($_command);
while (true) {
$sent = socket_write($sock, $_command, $length);
if ($sent === false) {
$this->mailError("Command Failed - " . $hostname, "Failed to write command to socket on " . $address . "\n\n" . socket_strerror(socket_last_error($sock)) . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback());
socket_shutdown($sock, 2);
socket_close($sock);
return false;
}
if ($sent < $length) {
$_command = substr($_command, $sent);
$length -= $sent;
}
else {
break;
}
}
socket_shutdown($sock, 1);
// Read back from socket
if (($out = socket_read($sock, 1024)) !== false) {
#socket_shutdown($sock, 0);
$out = trim($out);
if ($out !== "ok") {
$this->mailError("Command Failed - " . $hostname, "Message received back from socket was '" . $out . "' on " . $address . "\n\n" . socket_strerror(socket_last_error($sock)) . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback());
socket_close($sock);
return false;
}
}
else {
$this->mailError("Command Failed - " . $hostname, "Failed to read from socket on " . $address . "\n\n" . socket_strerror(socket_last_error($sock)) . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback());
socket_shutdown($sock, 0);
socket_close($sock);
return false;
}
socket_close($sock);
return $out;
}
For a simple socket client such as this, I much prefer fsockopen() - it drastically reduces the complication of the client code and does not require the sockets extension, which is not available everywhere. The main disadvantage to this is that you lose the string error messages, but these are rarely that useful anyway - you still get an error string from creating the socket, and if read/write operations fail it's because the socket has become disconnected.
I'm also not sure how useful "allow half open" is in this context - I think it is likely to create more problems than it solves. The calls to socket_shutdown() are not doing anything useful here (and may be the source of your problems) - you would only use this if you are operating on a socket that will not be closed and may be operated on at a later point in time by some other code. Since you create a new socket and destroy it in the same routine, this is not the case.
Here is your code rewritten to use fsockopen() - as you can see, it is somewhat shorter and simpler. I have also modified it slightly so that it always returns a bool - your code returns either bool FALSE or string ok, and since there are only these two options it makes more sense for the function to always return a bool.
public function sendDaemonCommand($address, $template_id, $params = array()) {
// Prepare data
$hostname = $this->getHostnameFromPrivateIP($address);
$port = 9000;
$command = array('template_id' => $template_id, 'params' => $params);
$_command = $command = json_encode($command);
$length = strlen($_command);
// Connect to socket
if (!$sock = fsockopen($address, $port, $errNo, $errStr)) {
$this->mailError("Command Failed - " . $hostname, "Failed to connect to socket on " . $address . "\n\n" . $errStr . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback());
return false;
}
// Write command to socket
while (true) {
// Try and write data to socket
$sent = fwrite($sock, $_command, $length);
// If it failed, error out
if ($sent === false) {
$this->mailError("Command Failed - " . $hostname, "Failed to write command to socket on " . $address . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback());
fclose($sock);
return false;
}
// If there is data left to send, try again
if ($sent < $length) {
$_command = substr($_command, $sent);
$length -= $sent;
continue;
}
// If we get here the write operation was successful
break;
}
// Read back from socket and close it
$out = fread($sock, 1024);
fclose($sock);
// Test the response from the server
if ($out === false) {
$this->mailError("Command Failed - " . $hostname, "Failed to read from socket on " . $address . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback());
return false;
} else if (($out = trim($out)) !== "ok") {
$this->mailError("Command Failed - " . $hostname, "Message received back from socket was '" . $out . "' on " . $address . "\n\nCommand:\n\n" . $command. "\n" . $this->functionTraceback());
return false;
} else {
return true;
}
}
Related
Using phpseclib from my PHP application, I am running a perl .pl script which creates a file on the remote server's drive/folder in one of the exec() commands. This works fine and the file exists. The next exec() does an 'ls' command to retrieve the full filename which contains a date/time stamp on the end of the file name that is unknown. When it runs, it doesn't find the file when it is there and I can see it when doing a manual 'ls' on the Linux system. I believe it might be a timing issue, so I also included a sleep 15 in between creation and doing the 'ls' command, but this did not resolve the issue. And if I run the program again, creating a 2nd file, then the 'ls' returns the filename for the first file I generated. I didn't see an fclose in the documentation, but did see it in some other posts here that might lead me to believe this might also be the issue. Has anyone else experienced this issue and how did you resolve? Thanks.
Updated: Code snippet added....
$ssh2->login(LINUX_USER,LINUX_PASS);
$connected = $ssh2->isAuthenticated();
if ($connected) {
//$_SESSION['feedback'] .= "Connection and authentication successful to " . LINUX_SVR . ".<br>";
$cmd_string = "/usr/bin/perl-report.pl " . LINUX_ENV . " " . $parm1 . " " . $parm2;
$error = $ssh2->exec($cmd_string);
if ($error) {
$_SESSION['feedback'] .= "Unable to execute command to create the Report - contact Operations with this error.<br>";
$_SESSION['feedback'] .= $ssh2->getLog();
} else {
$_SESSION['feedback'] .= "Report successfully created for ID " . $parm2 . ".<br>";
//get the full name of the file created
$cmd_string = "sleep 15";
$ssh2->exec($cmd_string);
$cmd_string = "ls /root/Report-" . $parm1 . "-" . $parm2 . "-" . $date . "*";
$error = $ssh2->exec($cmd_string);
if (empty($error)) {
$_SESSION['feedback'] .= "Unable to execute command to obtain report name - contact Operations with this error.<br>";
$_SESSION['feedback'] .= $ssh2->getLog();
} else {
$filename = $ssh2->exec($cmd_string);
$filename = substr($filename, 6, strlen($filename));
$_SESSION['feedback'].= "The file name created is " . $filename . "<br>";
//verify that the filename is valid
if (substr($filename,0,7)=="Report-") {
//mail the attachment to the emailaddr
$cmd_string = "echo 'Requested Report attached.' | mail -s '" . $filename . "' -a '/root/" . $filename . "' " . $emailaddr . "\n";
$error = $ssh2->exec($cmd_string);
if ($error) {
$_SESSION['feedback'] .= "Unable to successfully email the requested report - contact Operations with this error.<br>";
$_SESSION['feedback'] .= $ssh2->getLog();
} else {
$_SESSION['feedback'] .= "Report successfully emailed to " . $emailaddr . ". Process complete.<br>";
$_SESSION['feedback'] .= $ssh2->getLog();
}
} else {
$_SESSION['feedback'] .= "Unable to email attachment as filename is invalid - contact Operations with this error.<br>";
$_SESSION['feedback'] .= $ssh2->getLog();
}
}
}
} else {
$_SESSION['feedback'] .= "Connection and authentication failed to " . LINUX_SVR . ".<br>";
}
//disconnect when done
$ssh2->reset();
$ssh2->disconnect();
}
Hello All hopefully you guys don't shoot me with virtual guns if already asked but, here goes.
I am socket_send to an ip/port i see the server receives and responds with data back. My code receives xxxx bytes. I want to know what those bytes entail IE: xml string back so I can parse and use the data back in my application. cURL is not doable in this situation because the server receives headers and does not respond at all.
**Code:**
/* Create a TCP/IP socket. */
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) {
echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
} else {
echo "OK.\n";
}
/* Create a TCP/IP socket. */
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) {
echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
} else {
echo "OK.\n";
}
echo "Attempting to connect to '$address' on port '$port'...";
$result = socket_connect($socket, $address, $port);
if ($result === false) {
echo "socket_connect() failed.\nReason: ($result) " . socket_strerror(socket_last_error($socket)) . "\n";
} else {
echo "OK.\n";
}
$in = $xml;
$out = '';
echo "Sending HTTP HEAD request...";
socket_write($socket, $in, strlen($in));
echo "OK.\n";
socket_shutdown($socket, 1);
echo "Reading response:\n\n";
$buf = '';
if (false !== ($bytes = socket_recv($socket, $buf, 2048, MSG_WAITALL))) {
echo "Read $bytes bytes from socket_recv(). Closing socket...";
} else {
echo "socket_recv() failed; reason: " . socket_strerror(socket_last_error($socket)) . "\n";
}
socket_close($socket);
echo $buf . "\n";
echo "OK.\n\n";
?>
Response:
TCP/IP Connection
OK. OK. Attempting to connect to 'xxx.xxx.xxx.xxx' on port '9000'...OK. Sending HTTP HEAD request...OK. Reading response: Read 1365 bytes from socket_recv(). Closing socket... OK.
Firstly, you can use cURL and not send headers! Just an fyi.
Secondly, it's going to be tricky to determine exactly what type of content it is... Is the server returning any sort of "content type"? If so, you may be able to read that.
Afterwards, you can then use $buf and then attempt to use XML libraries to parse the string (such as PHP's SimpleXML Library).
If, for example, it doesn't parse the XML, then you know it's either a regular string or something else.
cURL should return the content type, however.
After crazy headaches and several medicine hits I found the issue: 1 line OMG
If any one else gets this issue with socket the line after socket_close($socket) I added echo html_entity_decode($buf).
I wrote a script to pull a server list from the master server from Enemy Territory; However it silently fails; I had it working; but for some unknown reason it doesn't anymore.
I used this document to "talk" with the server : http://src.gnu-darwin.org/ports/games/masterserver/work/masterserver-0.4.1/docs/PROTOCOLS
This seems valid since it worked before and it still works on game servers; This is my code :
<?php
// set sv_master1 "etmaster.idsoftware.com"
// set sv_master2 "master0.gamespy.com"
// set sv_master3 "wolfmaster.idsoftware.com"
// set sv_master4 "clanservers.net"
// set sv_master5 "master0.etmaster.net" 213.108.29.23
$host = 'etmaster.idsoftware.com';
$port = '27950';
// $status = chr(255) . chr(255) . chr(255) . chr(255) . chr(0x02) . 'getservers' . chr(0x00);
$status = "\xFF\xFF\xFF\xFFgetservers\x00";
$fp = stream_socket_client("udp://" . $host . ":" . $port, $errno, $errstr, 10);
if (!$fp) {
echo "ERROR: " . $errno . $errstr . "<br>\n";
} else {
fwrite($fp, $status);
//stream_set_timeout($fp, 10); // this is for debugging;
$data = fread($fp, 1024);
fclose($fp);
print_r($data);
}
I attempted 60 seconds execution time (its localhost) but it still doesn't work ... any help is appreciated !
When I check the steps (in cli) the longest time is spend on fread(); but $data contains only an empty string;
The error is small; there had to be 5* \xFF tag; and I only used 4.
I'm running a PHP (5.4.12) script on a Windows Server 2008 machine.
When the total concurrent connections hit 255, the script crashes.
On a PHP 5.2.x system the SocketServer end with an error:
"An operation was attempted on something that is not a socket."
PHP 5.4.12, does not crash but does not handle the connections anymore.
I'm using the following code, taken from the PHP.net website:
http://php.net/manual/en/sockets.examples.php
<?php
error_reporting(E_ALL);
set_time_limit(0);
ob_implicit_flush();
$address = '192.168.1.7';
$port = 8676;
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
echo "socket_create() last error " . socket_strerror(socket_last_error()) . "\n";
}
if (socket_bind($sock, $address, $port) === false) {
echo "socket_bind() last error " . socket_strerror(socket_last_error($sock)) . "\n";
}
if (socket_listen($sock, 5) === false) {
echo "socket_listen() last error " . socket_strerror(socket_last_error($sock)) . "\n";
}
$clients = array();
do {
$read = array();
$read[] = $sock;
$read = array_merge($read,$clients);
$write = NULL;
$except = NULL;
if(socket_select($read,$write , $except , $tv_sec = 5)===false)
{
echo(PHP_EOL.'\n\ncrash\n\n'.PHP_EOL);
exit();
}
if (in_array($sock, $read)) {
if (($msgsock = socket_accept($sock)) === false) {
echo "socket_accept() falló: razón: " . socket_strerror(socket_last_error($sock)) . "\n";
// break;
}
$clients[] = $msgsock;
$key = array_keys($clients, $msgsock);
$msg = "{$key[0]} welcome msg\n".PHP_EOL;
socket_write($msgsock, $msg, strlen($msg));
}
// Handle Input
foreach ($clients as $key => $client) { // for each client
if (in_array($client, $read)) {
if (false === ($buf = socket_read($client, 2048))) {
echo "socket_read() last error: " . socket_strerror(socket_last_error($client)) . "\n";
// break 2;
}
if (!$buf = trim($buf)) {
continue;
}
if ($buf == 'quit') {
$talkback = print_r($clients);
socket_write($client, $talkback, strlen($talkback));
unset($clients[$key]);
socket_close($client);
break;
}
if ($buf == 'shutdown') {
socket_close($client);
break 2;
}
$talkback = "Client {$key}: Echo: '$buf'.\n";
socket_write($client, $talkback, strlen($talkback));
echo "$buf\n";
}
}
} while (true);
socket_close($sock);
?>
I've tested the script by running "EchoServerTest.exe". I creates a number of concurrent connections.
First I thought it was a problem in WinSock, but I can't find that there is a limitation on 255 connections with WinSock.
I also changed the registry settings TcpNumConnections, TcpTimedWaitDelay and MaxUserPort.
I used the same code on a Ubuntu server, so the problem much be related to Windows.
I tested this on two different servers, both Windows Server 2008 (32bit and 64bit).
One locally and one on the internet. So firewall or other external problems should be ruled out.
I'm out of options.
Before you ask, do you really need so many connections? Yes.
There are many 'dead' connections, so I've altered the code. Now those 'dead' connection are now 'Timed-out'. So I have some time to solve this, but eventually the number of concurrent connections will reach 255 again.
Any ideas on this one?
I think the problem is in this line:
if (socket_listen($sock, 5) === false) {
You are manually restricting to 5 the number of backlog incoming connections queued for processing. In Linux this is silently truncated to SOMAXCONN, not that way on Windows. Try using SOMAXCONN instead of 5.
I read the post here
Test if port open and forwarded using PHP
about how to scan ports of the same proxy . But my problem is I want to do scan the same port of different ip xxx.xxx.xxx.$i and for loop i try to run it from 0 to 255 . I use the same script in the above mentioned post using for loop . But it takes too long to get the answer (actually I dont get any) . Here is the code
for($i=0;$i<2;$i++){
$host = 'xxx.xxx.xxx.'.$i;
$connection = #fsockopen($host, 3128);
if (is_resource($connection))
{
echo '<h2>' . $host . ':' . $port . ' ' . '(' . getservbyport($port, 'tcp') . ') is open.</h2>' . "\n";
fclose($connection);
}
else
{
echo '<h2>' . $host . ':' . $port . ' is not responding.</h2>' . "\n";
}
}