為什麼用fopen開啟遠端URL會很慢?
阿新 • • 發佈:2018-12-24
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秒呀。其中必有蹊蹺。- By analyzing it with Wireshark, the issue (in my case and probably
- yours too) was that the remote web server DIDN'T CLOSE THE TCP
- CONNECTION UNTIL 15 SECONDS (i.e. "keep-alive").
- Indeed, file_get_contents doesn't send a "connection" HTTP header, so
- the remote web server considers by default that's it's a keep-alive
- connection and doesn't close the TCP stream until 15 seconds (It might
- not be a standard value - depends on the server conf).
- A normal browser would consider the page is fully loaded if the HTTP
- payload length reaches the length specified in the response
- Content-Length HTTP header. File_get_contents doesn't do this and
- that's a shame.
問題出在file_get_contents的行為上:(1)它發起http連線的時候,沒有帶http頭,(2)它接收完所有資料後,沒有主動斷開和伺服器的http連線。 找到問題原因之後,解決方案就相對簡單了:讓file_get_contents也帶上一個http的頭,並且在頭裡面標註:Connection: close; 要求伺服器發完資料後立即斷開連線。 file_get_contents原型如下:
- string file_get_contents (
- string $filename [,
- bool $use_include_path = false
- [, resource $context
- [, int $offset = -1
- [, int $maxlen ]]]] )
其中$context引數就是用來傳遞http頭的。程式碼如下:
- $opts = array(
- 'http'=>array(
- 'method'=>"GET",
- 'Connection'=>"close\r\n"
- )
- );
- $context = stream_context_create($opts);
- 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。- curl_setopt($ch, CURLOPT_FILE, $fp);