Detecting peer disconnect (EOF) with PHP socket module - php

I'm having a weird issue with PHP's sockets library: I do not seem to be able to detect/distinguish server EOF, and my code is helplessly going into an infinite loop as a result.
Further explanation below; first of all, some context (there's nothing particularly fancy going on here):
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, '127.0.0.1', 8081);
for (;;) {
$read = [$socket];
$except = NULL;
$write = [];
print "Select <";
$n = socket_select($read, $write, $except, NULL);
print ">\n";
if (count($read)) {
print "New data: ";
#socket_recv($socket, $data, 1024, NULL);
$data = socket_read($socket, 1024);
print $data."\n";
}
print "Socket status: ".socket_strerror(socket_last_error())."\n";
}
The above code simply connects to a server and prints what it reads. It's a cut-down version of what I have in the small socket library I'm writing.
For testing, I'm currently using ncat -vvklp 8081 to bind a socket and be a server. With that running, I can fire up the code above and it connects and works - eg, I can type in the ncat window, and PHP receives it. (Sending data from PHP is working too, but I've excluded that code as it's not relevant.)
However, the moment I ^C ncat, the code above enters a hard infinite loop - and PHP says there's no error on the socket.
I am trying to figure out where the button is that whacks PHP upside the head and makes it realize that the peer has disconnected.
socket_get_status() is a great misnomer - it's an alias for stream_get_meta_data(), and it doesn't actually work on sockets!
feof() similarly spouts Warning: feof(): supplied resource is not a valid stream resource.
I can't find a socket_* function for detecting peer EOF.
One of the PHP manual notes for socket_read() initially dissuaded me from using that function so I used socket_recv() instead, but I eventually tried it just in case - but no dice; switching the receive call has no effect.
I have discovered that watching the socket for writing and then attempting to write to it will suddenly make PHP go "oh, wait, right" and start returning Broken pipe - but I'm not interested in writing to the server, I want to read from it!
Finally, regarding the commented part - I would far prefer to use PHP's builtin stream functionality, but the stream_* functions do not provide any means for handling asynchronous connect events (which I want to do, as I'm making multiple connections). I can do stream_socket_client(... STREAM_CLIENT_ASYNC_CONNECT ...) but then cannot find out when the connection has been established (6yo PHP bug #52811).

Okay, I figure I might as well turn the comments above into an answer. All credit goes to Ryan Vincent for helping my thick head figure this out :)
socket_recv will return 0 specifically if the peer has disconnected, or FALSE if any other network error has occurred.
For reference, in C, recv()'s return value is the length of the new data you've just received (which can be 0), or -1 to indicate an error condition (the value of which can be found in errno).
Using 0 to indicate an error condition (and just one arbitrary type of error condition, at that) is not standard and unique to PHP in all the wrong ways. Other network libraries don't work this way.
You need to to handle it like this.
$r = socket_recv($socket, $buf, $len);
if ($r === FALSE) {
// Find out what just happened with socket_last_error()
// (there's a great list of error codes in the comments at
// http://php.net/socket_last_error - considering/researching
// the ramifications of each condition is recommended)
} elseif ($r === 0) {
// The peer closed the connection. You need to handle this
// condition and clean up.
} else {
// You DO have data at this point.
// While unlikely, it's possible the remote peer has
// sent you data of 0 length; remember to use strlen($buf).
}

Related

IPC choice in PHP: Correct use of sockets

I have just dumped my earlier code, and am starting again from scratch, and would appreciate help ensuring that I am using sockets within PHP appropriately -- or whether another approach is warranted.
My main script is likely to be invoked from multiple processes concurrently, and for this task it is important that these requests are then 'collated' into a single work-stream, and processed in a linear fashion. I have what appears to be working code, below. What I would like to know is:
Are sockets the best approach here, or should I be using something else?
I found the plethora of different socket functions confusing, and not sure of advantages/disadvantages of each. Should I be using different functions?
Is my approach fundamentally 'correct'? I've tried to catch the 'obvious' problems, but have I thought of all of them?
I've heavily commented my code, which I hope helps to show what I would like help with. NB: This will be called via CLI (or, more precisely, php-fpm-cli), and is NOT web-server related. There is no timeout.
<?php declare(strict_types=1);
error_reporting(E_ALL);
# This script will be invoked multiple times by different processes at random times.
# It is important that there is no risk of a race condition.
$socket = socket_create(AF_UNIX, SOCK_STREAM, 0); // Left in at the moment, but unsure of best approach.
if ($socket === false) {
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Couldn't create socket: [$errorcode] $errormsg\n");
}
//if (socket_bind($socket, '/tmp/IPC.sock')) { // Earlier code, but stream_socket seemed easier.
$string = "Hello \n";
# First, we test to see if we are master, or client.
# Do this by attempting to create socket first
if (($sock = stream_socket_server('unix:///tmp/IPC.sock', $errorcode, $errormsg)) === false) {
echo "Couldn't create socket: [$errorcode] $errormsg, therefore we must be the client.\n";
if (($sock = stream_socket_client('unix:///tmp/IPC.sock', $errno, $errstr)) === false){
echo "Oh dear -- something is wrong! \n";
# Do what now??? Why would this happen? Is it a risk?
unlink('/tmp/IPC.sock');
}
# We need to pass our string to the main instance, and quit as quickly as possible.
# We don't want blocking, whilst threads are waiting for data to be received.
fwrite($sock, "World\n"); // Send data.
//echo stream_get_content($sock); // We don't actually NEED a response, unless it helps
// us to capture an error and take appropriate action.
//stream_socket_shutdown($sock); // Which one? Shutdown, or close?
fclose($sock);
echo "Done sending. \n";
} else {
# No matter how many things are happening in other processes or threads, this section
# should only ever be running once -- ensuring that we have strictly linear processing.
echo "Socket created OK. \n";
do_stuff ($string);
# Now see if we're received any more work to do.
# Keep going until it is all done.
while ($conn = stream_socket_accept($sock,1)) { // Should we be using a fifo?
$string= fread($conn, 1024); // Actually, I'm interested in the string up to a "\n".
do_stuff ($string);
}
//stream_socket_shutdown($sock); // Which one? Shutdown, or close?
fclose($sock); // Is there any risk of loss of incoming message at this point?
unlink('/tmp/IPC.sock');
}
function do_stuff($string){
echo "$string"; sleep(10); // Main code here. Delay NOT exaggerated. CPU intensive.
}
# Clear up, and go home!
?>
For background (if of any interest), my previous attempt started with the line
use parallel\{Runtime, Channel, Events};
and created multiple threads to split the task into chunks. It worked great, until I realised that I'd created the perfect environment for a DoS as CPU went to 100% for prolonged periods as soon as I had made more than a few calls to the main script!
This could be a question for CodeReview.SE (https://codereview.meta.stackexchange.com/questions/5777/), because the question is open-ended, and I'm not certain that I have the correct approach here (and therefore opinions matter, as does "Correctness in unanticipated cases").
I'm posting here because of the post:
Does being on-topic at another Stack Exchange site automatically make a question off-topic for Stack Overflow? states "Typically good CR questions are going to be too broad for SO". This question has specific goals: error trapping, CPU/Memory efficiency, and I am not 100% sure that my code "works as expected" (demonstrated by the unused remnants of socket_create(). So many functions that seem to do the same thing...), but I think my approach is broadly correct. It is specifically about IPC. There is no requirement for this code to be preserved verbatim.
However, I'm happy for the question to be migrated by Mods if considered appropriate, to prevent cross-posting exactly the same question twice.

Best way to execute commands to TCP/IP console from PHP

Before I start, excuse my english, I'm from Holland :)
I have a question regarding the use of PHP's fsockopen.
My Prerequisites
So basically, I have a Windows program running in the background which has a remote console over TCP/IP that I need to connect to so I can execute a few commands. I am able to connect to that console with KiTTY, and execute my commands without any problems.
My Solution
So the issue I have right now, is that I need to be able to execute these commands from the browser. I have searched the interwebs for best ways to do this and what I found was to use PHP's fsockopen to connect to my console. The code I tried is as follows:
$SOCKET = fsockopen("127.0.0.1", 12101, $errno, $errstr);
if($SOCKET){
echo "Connected!";
}
$firstRead = fread($SOCKET, 8000);
echo($firstRead);
And using fputs to send a command:
fputs($SOCKET, "HELP \r\n");
And after, reading out my response with this:
$response = fread($SOCKET, 8000);
echo $response;
The Problem(s)
But I have encountered a few weird problems when testing this.
As soon as I execute a command like "HELP", I can see from my KiTTY session that the command was executed and that I got a response, but when I read out the response with "fread" I get nothing. But when I use a loop to read it out like this, it reads something from the console at the second try almost everytime:
do {
$response = fread($SOCKET, 8000);
$i++;
} while (strlen($response) < 5 || $i < 5);
( Sometimes, it DOES read something from console on first try, but mostly it only reads something on second try ).
The Question
Now my question(s) is(are), why does it behave so strangely? And is it because I am doing something wrong? And is this really the best way to do this?
sidenote
When this works, I need to be able to call these PHP functions ( or something similar ) with a bunch of AJAX requests and get the response to show in the browser. This is an absolute MUST so please keep this in mind when writing a possible answer :)
Thanks everyone!
When you create a socket with fsockopen in PHP you might also want to specify if it is blocking or non-blocking, in case of a non-blocking socket the function socket_read will return false on error or if the connection was closed, or empty string until some data is received, in case of a blocking socket instead when you read on it, it will block until there is some data to read (or empty string if a timeout is hit).
The behavior you described seems to be non-blocking.
For changing the blocking type there are: socket_set_block and socket_set_nonblock.
When your code with sockets works, there won't be any problems with AJAX requests, but keep in mind to set a timeout in PHP socket, otherwise if the server is down or simply too slow the request will fail with error (a timeout from php if set_time_limit is exceeded, which is a fatal error, or a JavaScript one with the browser timeout constant).
Here are the links to manual of socket_read and socket_write, which I think are more appropriated of fread and fputs.

socket_recv not receiving full data

I'm sending from browser through Websocket an image data of around 5000 bytes but this line is receiving total of 1394 bytes only:
while ($bytes = socket_recv($socket, $r_data, 4000, MSG_DONTWAIT)) {
$data .= $r_data;
}
This is after handshake is done which is correctly being received. The json data is being cutoff after 1394 bytes. What could be the reason?
In the browser interface it is sending image as JSON:
websocket.send(JSON.stringify(request));
The browser interface is fine as it is working with other PHP websocket free programs I've tested.
Here is the full source code.
You have our socket set up as non-blocking by specifying MSG_DONTWAIT, so it will return EAGAIN after it reads the first chunk of data, rather than waiting for more data. Remove the MSG_DONTWAIT flag and use MSG_WAITALL instead, so that it waits for all the data to be received.
There are a few ways of knowing if you have received all the data you are expecting:
Send the length of the data. This is useful if you want to send multiple blocks of variable length content. For example if I want to send three strings, I might first send a "3" to tell the receiver how many string to expect, then for each one I would send the length of the string, followed by the string data.
Use fixed length messages. If you are expecting multiple messages but each one is the same size, then you can just read from the socket until you have at least that many bytes and then process the message. Note that you may receive more than one message (including partial messages) in a single recv() call.
Close the connection. If you are sending only one message, then you can half-close the connection. This works because TCP connections maintain separate states for sending and receiving, so the server and close the sending connection yet leave the receiving one open for the client's reply. In this case, the server sends all its data to the client and then calls socket_shutdown(1)
1 and 2 are useful if you want to process the data while receiving it - for example if you are writing a game, chat application, or something else where the socket stays open and multiple messages are passed back and forth. #3 is the easiest one, and is useful when you just want to receive all the data in one go, for example a file download.
1394 is around the common size of an MTU, especially if you are tunnelled through a VPN (are you?).
You can't expect to read all the bytes in one call, the packets may be fragmented according to the network MTU.
Just my 2 cents on this.
socket_recv can return false on an error. Where it can also receive zero (0) bytes in non-blocking IO.
Your check in your loop should be:
while(($bytes = socket_recv($resource, $r_data, 4000, MSG_DONTWAIT)) !== false) {}
Altough I would check the socket for errors also and add some usleep call to prevent "CPU burn".
$data = '';
$done = false;
while(!$done) {
socket_clear_error($resource);
$bytes = #socket_recv($resource, $r_data, 4000, MSG_DONTWAIT);
$lastError = socket_last_error($resource);
if ($lastError != 11 && $lastError > 0) {
// something went wrong! do something
$done = true;
}
else if ($bytes === false) {
// something went wrong also! do something else
$done = true;
}
else if (intval($bytes) > 0) {
$data .= $r_data;
}
else {
usleep(2000); // prevent "CPU burn"
}
}
I'm wondering if you are having issues with your websockets connection. The while-loop you quote above looks to me to reside in a part of the code where the client handshake has failed, it's in the else of if($client->getHandshake()) { ... } else { ... }.
As far as I can tell the $client is a separate class, so I can't see what the class looks like or what Client::getHandshake() does, but I'm guessing it is the getter of a boolean that holds the success or failure of the websocket upgrade handshake.
If I'm correct the handshake fails and the connection is closed by the client. From the code I can see that the server-code you are using requires version 13 of the spec. You do no mention which client-side library you are using, but other servers will accept other versions than this server.
Please make sure your client-library supports the latest version.
Posting the verbose output from the server when it gets an incoming connection and the transfer fails will be of help if what I'm suggesting is wrong.
BUT, isn't the portion of the code that you pasted contained in the else block? The else block that to me looks like the hand shake did not go through?
Could you print the received bytes as string?
I don't think your question is correct. According to the source code, if the handshake has succeeded then this section of code is executed:
$data = '';
while (true) {
$ret = socket_recv($socket, $r_data, 4000, MSG_DONTWAIT);
if ($ret === false) {
$this->console("$myidentity socket_recv error");
exit(0);
}
$data .= $r_data;
if (strlen($data) > 4000) {
print "breaking as data len is more than 4000\n";
break;
} else {
print "curr datalen=" . strlen($data) . "\n";
}
}
If the program really goes to the code section that you provided then it will be worth to look into why the handshake failed.
The Server Class has a third parameter verboseMode which when set to true will provide you with detailed debug logs on what exactly is happening.
We will just be speculating without the debug log, but if the debug log is provided we can come up with a better suggestion.

PHP Packets to a minecraft server?

Source for packet format: http://wiki.vg/Protocol
I am trying to write a program in php which will send packets to a minecraft server I host. This will likely be for a multiplayer implementation for phones, which do not support the same API formats. The code i have below is my failed attempt to make this work. How can i do this properly? The server is recognizing something, but its either not sending anything back, or something. This below is supposed to send 0x00, the handshake packet to request the stats on the server using the next state ID: 1. Minecraft should be responding with the same ID, but with the server motd, and the player count. For 1.7.2, which this will be for, it'll also send a server-icon.png. But i can safely ignore that data, as it'll be direct connections only.
What am I doing wrong, and how can I fix it?
Main.php
require("socket.php");
$f = getStat("zontreck.dyndns.biz",25565);
/*$za = 0x00;
$za[0] = 74;
$za[1] = "zontreck.dyndns.biz";
$za[2] = 25565;
$za[3] = 1;
*/
$za = array(0x00,"Protocol Version"=>73,
"Server Address"=>"zontreck.dyndns.biz",
"Server Port"=>25565,
"Next State"=>1);
$dd = doWrite(pack("a",$za),$f);
echo("Response from doWrite: " .$dd."\n");
echo("Sending: " . pack("a",$za)."\n");
/*if(doWrite(pack("a",$za),$f))
{
echo(fgets($f));
} else {
echo(fgets($f));
}*/
$ff = fgets($f);
//$u = unpack("a",$ff);
fclose($f);
echo("Data from server: " . $ff."\n");
echo("Data from server length: " . strlen($ff)."\n");
//fclose($f);
?>
socket.php
<?
function getStat($url = "",$port=25565)
{
$f = fsockopen($url,$port);
return $f;
}
function doWrite($packet,$socket)
{
return fwrite($socket,$packet);
}
?>
Output from main.php
root#zontreck:/var/www/slmc# php main.php
PHP Notice: Array to string conversion in /var/www/slmc/main.php on line 16
Response from doWrite: 1
PHP Notice: Array to string conversion in /var/www/slmc/main.php on line 18
Sending: A
Data from server:
Data from server length: 0
root#zontreck:/var/www/slmc#
You'll probably want to take a look at this:
https://forums.bukkit.org/threads/web-php-simple-to-use-minecraft-server-status-query.144329/
It's basically some explanation on this library which does exactly what you want:
https://github.com/FunnyItsElmo/PHP-Minecraft-Server-Status-Query
It also uses sockets so you could always take some of the code and modify it to your needs, but I strongly advice you just to use the library as-is because it does what you're trying to accomplish.
Both of the commenters are right, you need to revisit how you are doing this.
More specifically, the Minecraft protocol uses UDP, which means you need to have access to the raw socket using socket_create - http://php.net/manual/en/function.socket-create.php
Then you need to build a string that contains all of the of the required packets correctly, as #Marc B outlined. Then you can write that string to the socket. Note that since UDP is both stateless and has no error correction, you will also have to write retry routines if you get an error back.
What you are currently doing is building data structures in one language (PHP), then trying to write them to a wire protocol (totally different), which is pretty much impossible.
It's certainly possible to do this in PHP, but you have to work at a much lower level than you are now doing, with raw sockets, hex data strings and network error handling. I would suggest you look at other PHP libraries which handle UDP data to see if there is some code you can reuse.
There is some code in the socket_create page comments that you might be able to use/learn from. In particular, there is a TFTP example which you might be able to modify.
Not a simple answer, but this is non-trivial to do in any language and PHP isn't really designed for this, so there aren't a lot of examples.

PHP: how to use socket_select() and socket_read() properly

Edit: *Bug in test harness was causing me to misinterpret the results. socket_select() works exactly as you might expect: it really does wait until there is data ready on the socket. But it will report ready if you call it after the client closes the connection. Turns out it's all my fault, but I'll leave the question here in case anyone else ever suspects the behavior of socket_select().
I have a multi-threaded PHP application and I'm using sockets to communicate between the threads. The communication works pretty well, but I end up doing a lot of unnecessary reading of sockets that have no data ready. Perhaps there's something about socket programming that I'm missing.
The PHP doc for socket_select() says The sockets listed in the read array will be watched to see if a read will not block.
This is exactly the behavior I want: call socket_select() to wait until one of my child threads is trying to talk to me. But that only works for the very first time the thread writes, after I've accepted the connection. After that, socket_select() will forever say that the socket is ready, even after I've read all the data from it.
Is there some way I can mark that socket 'unready' so socket_select() won't report that it's ready until more data arrives? Or is it conventional to close that connection after I've read all the data, and await another connection request? I've done a lot of tutorial- and explanation-reading, but I haven't been able to figure out how to do this correctly. Maybe I'm missing something obvious?
In case it helps to see code, here's what I'm doing:
// Server side setup in the main thread
$this->mConnectionSocket = socket_create(AF_INET, SOCK_STREAM, 0);
$arrOpt = array('l_onoff' => 1, 'l_linger' => 0);
#socket_set_option($this->mConnectionSocket, SOL_SOCKET, SO_LINGER, $arrOpt);
#socket_set_option($this->mConnectionSocket, SOL_SOCKET, SO_REUSEADDR, true);
#socket_bind($this->mConnectionSocket, Hostname, $this->mPortNumber);
#socket_listen($this->mConnectionSocket);
#socket_set_block($this->mConnectionSocket);
.
.
.
// Then call listen(), which looks like this:
public function listen(&$outReadySockets) {
$null = null;
while(true) {
$readyArray = array_merge(array($this->mConnectionSocket), $this->mReceiverSockets);
socket_select($readyArray, $null, $null, $waitTime = null);
if(in_array($this->mConnectionSocket, $readyArray) === true) {
$this->acceptConnection();
$key = array_search($this->mConnectionSocket, $readyArray);
if($key === false) {
throw new IPCException("array_search() returned unexpected value");
} else {
unset($readyArray[$key]);
if(in_array($this->mConnectionSocket, $readyArray) === true) {
throw new IPCException("in_array() says the key is still there");
}
}
}
if(count($readyArray) > 0) {
$outReadySockets = array_merge($readyArray);
break;
}
}
}
// Client side setup in the child thread
$this->mSocket = #socket_create(AF_INET, SOCK_STREAM, 0);
#socket_set_block($this->mSocket);
#socket_connect($this->mSocket, Hostname, $this->mPortNumber);
.
.
.
#socket_write($this->mSocket, $inDataToWrite, $lengthToWrite);
// Main thread reads the socket until it's empty
$data = "";
$totalBytesRead = 0;
while($totalBytesRead < $inNumberOfBytesToRead) {
// Strange that even if we set the socket to block mode, socket_read()
// will not block. If there's nothing there, it will just return an
// empty string. This is documented in the PHP docs.
$tdata = socket_read($inSock, $inNumberOfBytesToRead);
if($tdata === false) {
throw new IPCException("socket_read() failed: " . socket_strerror(socket_last_error()));
} else {
$data .= $tdata;
$bytesReadThisPass = strlen($tdata);
if($bytesReadThisPass === 0) {
break;
}
}
$totalBytesRead += $bytesReadThisPass;
}
.
.
.
// Then calls listen() again
As I say, it works great, except that when I call listen() a second time, it tells me that the socket is still ready. That seems to be what the PHP doc is saying, but I don't want that. I want to know when there really is data there. Am I doing it wrong? Or just missing the point?
You are misusing the socket functions.
First of all, socket_read will return false on error; your code does not check for this and will treat error returns the same as empty strings (this is an artifact of the specific constructs you are using, i.e. string concatenation and strlen).
Another problem is that when a connection attempt is detected you are clearly violating the instructions for socket_select:
No socket resource must be added to any set if you do not intend to
check its result after the socket_select() call, and respond
appropriately. After socket_select() returns, all socket resources in
all arrays must be checked. Any socket resource that is available for
writing must be written to, and any socket resource available for
reading must be read from.
Instead of this, if a connection is detected the code accepts it and goes back to socket_select again; sockets ready for reading are not serviced.
Finally it looks like you are confused about what EOF means on a socket: it means that the client has closed its write end of the connection. Once socket_read returns the empty string (not false!) for the first time, it will never return anything meaningful again. Once that happens you can send data from the server's write end and/or simply close the connection entirely.

Categories