PHP的fsockopen函數詳解

        先來看看手冊是如何定義fsockopen函數的。
        fsockopen — 打開一個網絡連接或者一個Unix套接字連接。

resource fsockopen ( string $hostname [, int $port = -1 [, int &$errno 
[, string &$errstr [, float $timeout = ini_get("default_socket_timeout") ]]]] )

        使用fsockopen方法和使用CURL方法的實質都是去模擬HTTP協議,所以重點在於如果去構造HTTP協議。

        注意:要使用此函數的功能,需要在PHP.ini文件中打開allow_url_open

//打開連接通道
$fp=fsockopen('www.cnblogs.com',80,$errno,$errstr,30);
if(!$fp){
    die('連接錯誤'.$errstr);
}
set_time_limit(0);
//通道打開,模擬HTTP請求,http協議標準的換行是\r\n
$http="GET / HTTP/1.1\r\n";
$http.="Host: www.cnblogs.com\r\n";
$http.="User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
$http.="Connection: Close\r\n\r\n";

//http模擬完成,發送給服務器
fwrite($fp,$http);

//接受服務器的返回結果
$out='';
while(!feof($fp)){
    $out.=fread($fp,1024);
}

echo $out;
fclose($fp);

        在開發過程中常常遇到這樣的需求,模擬瀏覽器訪問某接口,並獲取返回數據。我們比較常使用的方法是fsockopen與接口建立連接,然後發出指令,然後通過fgets接受返回值。

        但是我們發現,通過PHP模擬訪問接口往往比瀏覽器訪問同樣的接口慢很多。這個問題困擾過我很久,今天終於找到原因了。我看網上很多朋友有同樣的問題,分享出來供大家參考。

        我們常常寫這樣的代碼:

while(!feof($sHnd)) {
    $line = fgets($sHnd, 4096);
}

        fgets會獲取文件描述符$sHnd的當前的4096(也可能是別的常數)個字節,如果還沒有到4096個字節遇到換行符了,則只返回換行符及換行符之前的內容。

        許多文檔教程裏也都是這麼講的,這段代碼許多情況下也能正常執行。我一步一步跟蹤PHP語句的耗時,發現前面若干次的fgets都很快,最耗時的是最後一次fgets,有時長達幾秒,有時長達十幾秒。

        原來這是服務器的KeepAlive功能造成的,Apache有這麼一個設置(nginx等其他web服務器也都有):KeepAlive,如果這個設置置爲On,則完成一次請求後,服務器並 不會關閉TCP連接,而是保持連接等待瀏覽器下次發起其他請求時直接利用這個連接。但是當fgets獲取最後一段內容時沒有發現換行符,也沒有文件結束標誌(feof()),所以fgets獲取完內容後仍繼續等待,希望遇到換行符或者其他內容以達到4096個字符。於是,就這樣服務器和PHP耗上了,互相 等待。耗了一會後,服務器先耗不起了,畢竟服務器的連接數很寶貴,當連接若干秒沒有活動,就會關掉這個連接(Apache通過KeepAliveTimeout這個選項進行設置,這個值通常爲5-15)。服務器關掉連接之後,PHP這邊的fgets這才失落得返回最後一批內容, 訪問接口過程結束。

        清楚了慢的原因就知道了解決方案了:

服務器返回的HTTP頭中包含有內容長度這個屬性,當已接受的內容長度與之相等時,我們就可以斷定:接口內容已經獲取完畢,不必再等了。具體做法 是:每次獲取不超過剩餘總長度的內容(min(4096, $leftlength))。剩餘總長度爲0時,跳出while(feof($xxxxx))的循環。

        經過這樣的修改,php通過sock方式訪問接口速度慢的問題已經基本解決了,但還不夠完美,繼續速度優化的思路還在KeepAlive上。

        大家都知道訪問接口的耗時相當一部分是浪費在建立連接上,如果我們需要頻繁調用接口的話,還有很大的優化餘地。既然服務器保持了連接,那如果PHP 也把連接保存下來那是不是就不用建立連接了?答案是肯定的:第一次訪問接口時使用pfsockopenpfsockopenfsockopen唯一的 區別就是它建立的是長連接)函數建立與服務器的連接,在訪問完成後不關掉(fclose)連接,以後的訪問就直接使用這個連接。具體到代碼裏就是:先判斷 有沒有連接,如果有,繼續用,如果沒有,建立pfsockopen連接。

        另外,如果接口返回內容比較短(比如:小於50字符)的話,還有優化的餘地,那就是在HTTP請求頭的Accept-Encoding中去掉 gzip。它的作用是告訴服務器,我(瀏覽器)可以接受壓縮過的內容,如果服務器也支持gzip就壓縮後再返回,瀏覽器得到內容後解壓縮再顯示。但是如果 內容太短的話,壓縮後體積反而會增加,再加上壓縮、解壓縮的時間,就更加得不償失了。

        經過以上幾步,訪問接口速度應該與瀏覽器一樣,理論上還會稍微快一點點。

        原文地址:http://phptianye.blog.51cto.com/10197989/1738500

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章