1. 程式人生 > >為什麼用fopen開啟遠端URL會很慢?

為什麼用fopen開啟遠端URL會很慢?

1. HTTP HEADER之詭

$content = file_get_contents('http://www.baidu.com/logo.png');
這是php中一種讀取網路圖片的方式,就跟讀本地檔案一樣,用起來十分方便。但是這種方式存在一個問題,部分網路地址讀起來速度非常快,但總有少數網址,讀起來要卡頓10秒、20秒甚至1分鐘。網上對於這個問題有很多的討論,但答案基本都有問題。 對於慢,最常見的解釋是DNS查詢慢。的確,通過curl和wget的對比可以發現,獲取同一個檔案,curl需要0.5s,而wget需要1.5s,從wget的螢幕輸出上看,其中1秒鐘被他花在了DNS查詢上。雖然DNS查詢會佔據1s的時間,但也不應該卡頓10秒呀。其中必有蹊蹺。
國外有個同學終於給出了一個靠譜的答案:
  1. By analyzing it with Wireshark, the issue (in my case and probably  
  2. yours too) was that the remote web server DIDN'T CLOSE THE TCP  
  3. CONNECTION UNTIL 15 SECONDS (i.e. "keep-alive").  
  4. Indeed, file_get_contents doesn't send a "connection" HTTP header, so  
  5. the remote web server considers by default that's it's a keep-alive  
  6. connection and doesn't close the TCP stream until 15 seconds (It might  
  7. not be a standard value - depends on the server conf).  
  8. A normal browser would consider the page is fully loaded if the HTTP  
  9. payload length reaches the length specified in the response  
  10. Content-Length HTTP header. File_get_contents doesn't do this and  
  11. that's a shame.  

問題出在file_get_contents的行為上:(1)它發起http連線的時候,沒有帶http頭,(2)它接收完所有資料後,沒有主動斷開和伺服器的http連線。 找到問題原因之後,解決方案就相對簡單了:讓file_get_contents也帶上一個http的頭,並且在頭裡面標註:Connection: close; 要求伺服器發完資料後立即斷開連線。 file_get_contents原型如下:
  1. string file_get_contents (  
  2.   string $filename [,   
  3.   bool $use_include_path = false  
  4.   [, resource $context
  5.   [, int $offset = -1  
  6.   [, int $maxlen ]]]] )  

其中$context引數就是用來傳遞http頭的。程式碼如下:
  1. $opts = array(  
  2.   'http'=>array(  
  3.     'method'=>"GET",  
  4.     'Connection'=>"close\r\n"
  5.   )  
  6. );  
  7. $context = stream_context_create($opts);  
  8. file_get_contents($filename, false, $context);  

新增上$context後,效能立即得到了提升,每次讀請求只需要1~2秒了。 注1:file_get_contents實際上是對fopen、fread、fclose的一個封裝,所以它存在的問題,在fopen上同樣存在。對於fopen來說,解決方案也是相同的。

2. DNS之謎

經過新增$option,效能已經得到了很大的提升。這時候DNS查詢帶來的開銷就不可忽視了。如何消除掉這1秒的開銷?目前我還沒有基於fopen、file_get_contents的方案,只能用curl方案了。curl方案既不存在上面的HTTP HEADER問題,也不存在DNS問題,是個非常不錯的方案。 如果你選擇用curl方案,同時還希望用上stream介面來讀資料,可以瞭解一下curl的CUROPT_FILE這個option。
  1. curl_setopt($ch, CURLOPT_FILE, $fp);  


3. 總結

本文提出瞭解決網路讀取圖片慢的兩種方案,對於移動開發、爬蟲開發,微信開發,支付寶開發等各領域的朋友都有借鑑意義。 本文還讓我重新思考了一個方法論:當網上搜不到答案時,外國友人用Wireshark監聽資料包的方式其實是最直接合理高效的。為什麼我沒有這麼做?想想,還是以前總結出來的工具論,我對這些工具的使用不熟練,不會往這個方向上想,就算想了,也不願意行動。應該多動手,多學習!