2014-09-11 30 views
1

我試圖在PHP中打開一個非阻塞流(5.3.2 & 5.4.4)。我做到以下幾點:fopen在URL中的腳本在PHP中執行

$fp = fopen($url, 'r'); 
if ($fp === false) 
    return false; 
print('stream opened'.PHP_EOL); 
stream_set_blocking($fp, 0); 

URL指向一個PHP文件:

<?php sleep(10); ?> 
<html><body>Hello</body></html> 

的問題是,fopen()函數似乎要禁止我甚至能夠建立流爲非阻塞之前。實際上,stream opened消息在10秒後打印,而不是直接打印。

+1

根據'stream_set_blocking'上的文檔,它只支持套接字和普通文件。你的URL資源不是那些。 – phpisuber01 2014-09-11 13:39:40

+0

不是TCP連接的套接字流嗎?無論如何,這是封鎖在這裏(我讀了10秒後'流'打開的信息),而不是freads()。 – Mat 2014-09-11 14:01:35

+0

您的'fopen'正在等待您的URL發生握手。它等待10秒,因爲你的PHP腳本有一個「睡眠(10)」。流需要握手,然後持續打開連接。您的PHP腳本正在等待10秒,發送響應並立即關閉連接。這是PHP的默認行爲。您可以使用PHP創建套接字流,但不會成爲雙行餐巾腳本。 – phpisuber01 2014-09-11 14:32:56

回答

1

當在網址上做一個fopen時,HTTP標頭在那個時候發送。由於沒有上下文被拒絕(並且不可能使用非阻塞選項配置上下文),所以fopen等待發送http報頭和塊。 解決方法是使用fsockopen,它只打開tcp連接,不起作用。這種方法的缺點是必須手動創建HTTP請求。

這是一個(可優化的)實現,它以非阻塞的方式從url中讀取數據。

function parse_http_url($url) 
{ 
    $parts = parse_url($url); 
    if ($parts === false) return false; 
    if (!isset($parts['scheme'])) 
     $parts['scheme'] = 'http'; 
    if ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https') 
     return false; 
    if (!isset($parts['port'])) 
     $parts['port'] = ($parts['scheme'] === 'http') ? 80 : 443; 
    if(!isset($parts['path'])) 
     $parts['path'] = '/'; 
    $parts['uri'] = $parts['path']; 
    if (!empty($parts['query'])) 
     $parts['uri'] .= '?'.$parts['query']; 
    return $parts; 
} 

function url_get_contents($url, $options = null) { 
    if(!($url_parts = parse_http_url($url))) return false; 
    $timeout = intval(@$options['http']['timeout']); 
    if (!($fp = fsockopen($url_parts['host'], $url_parts['port'], $errno, $errstr, $timeout))) return false; 
    stream_set_blocking($fp, 0); 
    if($timeout > 0) { 
     stream_set_timeout($fp, $timeout); 
     $sleep_time = (($timeout * 1000000)/100); # 1% of timeout in ms 
     $stop_time = microtime(true) + $timeout; 
    } else { 
     $sleep_time = 10000; # 10 ms 
    } 
    if (!isset($options['http']['method'])) $options['http']['method'] = 'GET'; 
    if (!isset($options['http']['header'])) $options['http']['header'] = ''; 
    $request = "{$options['http']['method']} {$url_parts['uri']} HTTP/1.1\r\n{$options['http']['header']}\r\n"; 
    if (fwrite($fp, $request) === false) { 
     fclose($fp); 
     return false; 
    } 
    $content = ''; 
    $buff_size = 4096; 
    do { 
     $rd = fread($fp, $buff_size); 
     if ($rd === false) { 
      fclose($fp); 
      return false; 
     } 

     $content .= $rd; 

     $meta = stream_get_meta_data($fp); 
     if ($meta['eof']) { 
      fclose($fp); 
      if(empty($content)) return false; 
      // HTTP headers should be separated with \r\n only but lets be safe 
      $content = preg_split('/\r\n|\r|\n/', $content); 
      $resp = explode(' ', array_shift($content)); 
      $code = isset($resp[1]) ? intval($resp[1]) : 0; 
      if ($code < 200 || $code >= 300) { 
       $message = isset($resp[2]) ? $resp[2] : 'Unknown error'; 
       trigger_error("Error {$code} {$message}", E_USER_WARNING); 
       return false; 
      } 
      // Skip headers 
      while (!empty($content) && array_shift($content) !== ''); 
      return implode("\n", $content); 
     } 
     if ($meta['timed_out']) { 
      fclose($fp); 
      return false; 
     } 
     if (isset($stop_time) && microtime(true) >= $stop_time) { 
      fclose($fp); 
      return false; 
     } 

     if ($meta['unread_bytes'] === 0) { 
      usleep($sleep_time); 
     } 
    } while(true); 
}