Jan Żankowski
Jan Żankowski

Reputation: 8941

How to detect PHP pfsockopen being closed by remote server?

I'm trying to make asynchronous HTTP POST requests from PHP server to an Nginx server. I read this very informative blog post about the issue, decided to use the technique with pfsockopen function, and imported the code doing this written by the blog author. I include the key sections below:

private function createSocket() {
  if ($this->socket_failed)
    return false;
  $protocol = $this->ssl() ? "ssl" : "tcp";
  $host = $this->options["host"];
  $port = $this->ssl() ? 443 : 80;
  $timeout = $this->options["timeout"];
  try {
    # Open our socket to the API Server.
    # Since we're try catch'ing prevent PHP logs.
    $socket = @pfsockopen($protocol . "://" . $host, $port, $errno,
                         $errstr, $timeout);
    # If we couldn't open the socket, handle the error.
    if (false === $socket) {
      ...
      return false;
    }
    return $socket;
  } catch (Exception $e) {
    ...
    return false;
  }
}

private function makeRequest($socket, $req, $retry = true) {
  $bytes_written = 0;
  $bytes_total = strlen($req);
  $closed = false;
  # Write the request
  while (!$closed && $bytes_written < $bytes_total) {
    try {
      # Since we're try catch'ing prevent PHP logs.
      $written = @fwrite($socket, substr($req, $bytes_written));
    } catch (Exception $e) {
      ...
      $closed = true;
    }
    if (!isset($written) || !$written) {
      $closed = true;
    } else {
      $bytes_written += $written;
    }
  }
  # If the socket has been closed, attempt to retry a single time.
  if ($closed) {
    fclose($socket);
    if ($retry) {
      $socket = $this->createSocket();
      if ($socket) return $this->makeRequest($socket, $req, false);
    }
    return false;
  }
  $success = true;
  ...
  return $success;
}

It mostly works very well, but I'm observing the following problem. Usually, after about 30 seconds of inactivity, when I send a request this way, Nginx responds with a TCP RST (reset) packet, which I read indicates that the socket has been closed on the remote end. I can also see the connection statuses with netstat -na | grep 8080 and by that time, the connection PHP -> Nginx is in state CLOSE_WAIT, while Nginx -> PHP is in FIN_WAIT2. This guide confirms that this means the remote server wants to close the connection.

I know that the issue is caused by the connection timeout settings on the remote end, because when I increase it, connections shown by netstat remain in state ESTABLISHED for longer, and if PHP server decides to reuse such a connection, it works fine. However, I don't want to set connection timeout to a very high value, as I'm worried of running out of ports.

Is there any way I can detect in PHP that a connection went into a CLOSE_WAIT state? I tried the following and none of it works:

Upvotes: 1

Views: 1180

Answers (2)

ulix
ulix

Reputation: 373

AFAIK the best way is to select for read and, if readable, do an fread().

// $socket was opened via pfsockopen(...)
stream_set_blocking($socket,0);
while(1){
  // the do..while(0) is a trick to break out
  do{
     $rs = array($socket); $ws = NULL; $es = NULL;
     $ok = stream_select($rs,$ws,$es,0,100000);
     if ( $ok === false ) break; // select err, close and reconn
     if ( $ok <= 0 ) break 2; // select tout, conn is up/ok
     $rply = fread($socket,1024); // make one/two fread() 
     if ( $rply === false ) break; // read err
     if ( strlen($rply) == 0  ) break; // conn reset by peer
     break 2; // conn is up/ok
  }while(0); 
  // here i must reconn
  fclose($socket);
  $socket = pfsockopen(...);
  if ( $socket ) stream_set_blocking($socket,0);
  else return false;
  break;
}//endwhile1
// here the conn is up/ok
// check if some in $rply to be handled...

Upvotes: 0

Khriz
Khriz

Reputation: 5988

That's the one-million question, from what I've read the only way to check that is doing fread($socket) and if returned value is false there has been an error, that could be millions of things but if the remote server has closed the connection the fread will always return false. Sorry if this disappoints you, but I think there's no other way.

Upvotes: 1

Related Questions