3

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;
}
6
  • Well you shouldn't use PHP sockets to connect to Node.js, that's really inefficient when dealing with large numbers of opened connections. Commented Jan 5, 2012 at 15:01
  • Have you looked at netstat on the PHP server side ... maybe you are running out of local ports? Commented Jan 5, 2012 at 15:08
  • @alessioalex what else could I use on PHP end? Commented Jan 5, 2012 at 15:17
  • @EugenRieck isn't node JS supposed to be non-blocking and can handle simultaneous connections? Commented Jan 5, 2012 at 15:20
  • I am talking of the PHP end, not the JS end: The connection uses a local port, and (localIP,localPort,remoteIP,remotePort) tuples must be unique, so while NodeJS handles the remoteIP,remotePort part of this, you might run out of local ports - socket options can help you there, but please help diagnosis by looking at netstat! Commented Jan 5, 2012 at 15:24

1 Answer 1

1

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;
    }

}
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks, I have changed my code however in testing it's gone from instantly submitting the command to a 30 second delay. I think the problem is on Node JS end, it doesn't know when the data has ended which is why its holding open my script, changing the 'allow half open' option makes no difference. My Node JS script is at pastebin.com/r9big65J
Further testing reveals its being held up at the point of the fread if that helps..
@fire I see where your problem is and it is indeed at the Node end. This is what your socket_shutdown() was doing, terminating the command by closing the socket. There is no way to mimic this using the fsockopen() stream, however... What I would do for something like this is issue a terminating character that never appears in the command, such as a new line, after the command. So you send the command immediately followed by a new line, and have Node read data until it hits that new line. Many protocols, for example FTP, work in this fashion. I believe it is a better/easier approach.
Ok so your saying I should do $command = json_encode($command) . "\n" and in node when it recieves the data it should check if the last character is \n and if so call the socket end function?
Yep, that's what I would do - if you're doing this sort of thing in PHP it's another reason why streams are easier to work with than sockets, because you can just use fgets() - which will wait for the EOL before returning data.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.