0

I'm sorry if this has been asked before. I've looked everywhere but can't find any solution to my issue.

I have written a socket client function for a project of mine, and I would like to add a timeout to it so it doesn't take forever to load if it failed.

I've tried quite a few things suggested by the docs and other answers on StackOverflow, but nothing has worked thus far.

Here's my function;

    public function send($cmd, $host, $port){

        if(!($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)))
        {
            $errorcode = socket_last_error();
            $errormsg = socket_strerror($errorcode);

            die("Error (#300)");
        }

        if(!socket_connect($socket, $host, $port))
        {
            $errorcode = socket_last_error();
            $errormsg = socket_strerror($errorcode);

            die("Error (#400)");
        }

        if(!socket_write($socket, $cmd, strlen($cmd)))
        {
            $errorcode = socket_last_error();
            $errormsg = socket_strerror($errorcode);

            die("Error (#301)");
        }

        $reply = socket_read($socket, 4096)
                or die("Error (#302)");

        socket_close($socket);
        return $reply;
    }

I've tried the following things;

  • set_time_limit() - Doesn't work. Takes 20 seconds to load but shows "Maximum execution time of..."
  • ini_set("default_socket_timeout") - Still takes 20 seconds to load.
  • socket_set_nonblock() - This works, but makes socket_connect() fail.
  • https://stackoverflow.com/a/16939666/3457242 - Same as above. socket_set_nonblock() makes socket_connect() fail.
  • socket_set_option() - Takes 20 seconds to load. Doesn't seem to work.

The only thing that seems to work is the socket_set_nonblock() function, but it always make socket_connect() return false. I just can't figure it out. Any help would be appreciated.

Thanks!

2 Answers 2

1

What you could do is a while loop.

For example:

socket_set_nonblock($socket)
    or die("not able to set socket to nonblock");
$timeout = 5;
$startTime = time();
while(!socket_connect($socket, $host, $port))
{
    if ((time() - $startTime ) >= $timeout)
    { 
        die('timeout');

You could even get the current "socket_last_error" command in the while loop. http://www.php.net/manual/en/function.socket-last-error.php

Then, before the time check IF statement, you could get the error. If the error is 114 OR 115, it should do the time check error, otherwise you can kill it instantly with the error type. For example, 100: network is down.

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

5 Comments

Thanks for your answer, but I am still getting the same 20 second timeout as I did with everything else. Not sure why this is happening. It's not only my local WAMP installation. It's the same on my VPS.
while(!socket_connect($socket, $host, $port)) should be the while loop. (missed the !). And if its not in the IF statement, you should "continue" it, simply by continue; .
You have to use a non-blocking socket for this to work. And, you better check the error codes (like suggested) and use socket_select instead of busy looping, see the other answer.
@SteffenUllrich yes, good point. I added the nonblock function.
Thanks for your help guys! It works now on my VPS but not my local WAMP installation.
1

I'm not experienced with PHP, but I've written enough non-blocking code on C/Perl level. It looks like PHP does not have an easy way (like with Perl) to do what you want, so you have to code everything yourself like in C. From the linux manual page of connect(2):

   EINPROGRESS
    The socket is nonblocking and the connection cannot be completed immediately.  
    It is possible to select(2) or poll(2) for completion by selecting the socket for 
    writing. After  select(2)  indicates  writability,  use getsockopt(2)  to read the 
    SO_ERROR option at level SOL_SOCKET to determine whether connect() completed 
    successfully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of the usual 
    error codes listed here, explaining the reason for the failure).

translated to PHP this would be:

  • set the socket non-blocking
  • try connect (e.g. PHP socket_connect)
  • if the connect returns EINPROGRESS or EALREADY use select (in PHP socket_select) to wait until the socket gets writeable. Here you can give a timeout.
  • if the select ends because of timeout the connection failed within the timeout
  • if select succeeded (e.g. no timeout) check socket error with socket_last_error, it should be no error if the connect succeeded
  • set the socket blocking again, at least if the rest of your code expects a blocking socket

There are various examples out there which do work but do not use socket_select. They either sleep/usleep with a fixed time (and thus need more time to finish connect) or do a busy loop (and just burn CPU time, which might affect other applications).

Comments

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.