如何检测远程服务器关闭的PHP pfsockopen?

时间:2016-01-13 14:34:33

标签: php sockets nginx tcp

我正在尝试从PHP服务器向Nginx服务器发出异步HTTP POST请求。我阅读了关于这个问题的非常翔实的blog post,决定使用pfsockopen函数的技术,并导入由博客作者编写的code。我在下面列出了关键部分:

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

它主要运作良好,但我观察到以下问题。通常,在大约30秒不活动之后,当我以这种方式发送请求时,Nginx会响应TCP ​​RST(重置)数据包,I read表示套接字已在远程端关闭。我还可以看到与netstat -na | grep 8080的连接状态,到那时,连接PHP - &gt; Nginx处于状态CLOSE_WAIT,而Nginx - > PHP位于FIN_WAIT2This guide确认这意味着远程服务器想要关闭连接。

我知道问题是由远程端的连接超时设置引起的,因为当我增加它时,netstat显示的连接状态ESTABLISHED保持更长时间,如果PHP服务器决定重用这种连接,它工作正常。但是,我不想将连接超时设置为非常高的值,因为我担心端口耗尽。

我有什么方法可以在PHP中检测到连接进入CLOSE_WAIT状态?我尝试了以下操作,但没有一个有效:

  • 检查pfsockopen返回的句柄是否为false。
  • 检查由$errno撰写的pfsockopen变量。
  • 尝试捕捉来自pfsockopenfwrite的例外情况。
  • 检查fwrite($socket)是否返回false。
  • 检查fflush($socket)是否返回false。
  • 检查stream_get_meta_data($socket)['timed_out']

2 个答案:

答案 0 :(得分:1)

这是一百万个问题,从我读过的唯一方法来检查是在做fread($socket)如果返回的值是假的,就会出现错误,这可能是数以百万计的事情,但如果远程服务器已关闭连接,fread将始终返回false。对不起,如果这令你失望,但我认为别无他法。

答案 1 :(得分:0)

AFAIK最好的方法是选择读取,如果可读,则执行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...