這兩天一直在搞windows下nginx+fastcgi的file_get_contents請求。我想,很多同學都遇到當file_get_contents請求外網的http/https的php檔案時毫無壓力,比如echo file_get_contents(‘http://www.baidu.com’),它會顯示百度的頁面。但當你請求localhost/127.0.0.1本地網路的php服務時卻一直是timeout,無論你將請求時間和指令碼執行時間多長都無法返回資料,如file_get_contents(‘http://localhost/phpinfo.php’)。然而當你嘗試請求html這樣的靜態檔案時卻完全沒有問題。是什麼原因呢?!
首先,我們知道file_get_contents/curl/fopen開啟一個基於tcp/ip的http請求時,請求資料傳送到nginx,而nginx則委託給php-cgi(fastcgi)處理php檔案,一般情況fastcgi處理完一個php請求後會馬上釋放結束訊號,等待下一個處理請求(當然也有程式假死,一直佔用資源的情況)。開啟nginx.conf,我們看到下面這一行:
location ~ \.php {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME d:/www/htdocs$fastcgi_script_name;
include fastcgi_params;
}
上面已經清楚地看到,所有使用php結尾的檔案都經過fastcgi處理,而在php.ini的配置檔案中也有一句:
cgi.force_redirect = 1
表明,所有php程式安全地強制轉向交給cgi處理。
但在windows中,本地127.0.0.1:9000怎樣與php-cgi聯絡的呢?!答案是增加一個php-cgi程序,用它來監聽127.0.0.1:9000。通過控制器命令:
RunHiddenConsole.exe D:/www/php/php-cgi.exe -b 127.0.0.1:9000 -c C:/WINDOWS/php.ini
我們就可以在啟動windows時,開啟一個php-cgi.exe程序監聽來自127.0.0.1:9000 的請求。在dos命令下開啟netstat –a就可以看到本地計算機下的9000埠處於listening狀態(也就是空置,如果沒有傳送任何請求的話)。
好了,該說說在php中使用file_get_contents()、curl()、fopen()函式訪問localhost時為什麼不能返回結果。我們再來試驗在index.php中加入file_get_contents(‘http://127.0.0.1/phpinfo.php’)語句向phpinfo.php傳送一個請求,這時瀏覽器中的狀態指示一直在打轉,表示它一直在工作中。開啟Dos中的netstat命令,可以看到本地的9000埠的狀態為:ESTABLISHED,表示該程序在聯機處理中。實際上,這裡我們已經同時向nginx傳送了兩個基於http的php請求,一個是解析index.php,而另一個是phpinfo.php,這樣矛盾就出來了,因為我們的windows系統只加載了一個php-cgi程序,因此,它無法同時處理php請求,它只能先處理第一個請求(index.php),而index.php卻又在等待phpinfo.php處理結果,phpinfo.php沒人幫它處理請求,因為它一直在等待index.php釋放結束訊號,因此,造成了程式的阻塞狀態,陷入了死迴圈。所以我們就看到了瀏覽器的狀態指示一直在打轉。Curl()與fopen函式的原因也相同。
找到了原因,我們也就有了解決辦法。
一是,向系統增加一個php-cgi程序,當一個php-cig繁忙時,它能夠處理其它php請求。這時需給另一個fastcgi程序分配同一ip的不同埠,比如9001。為了解決兩個程序的工作分配情況,在nginx中可以通過upstream模組處理它們的工作權重,這也是很多網站實現負載均衡的解決方案:
upstream bakend {
server 172.0.0.1:9000 weight =5 max_fails=0 fail_timeout=30s;
server 172.0.0.1:9001 weight =5 max_fails=0 fail_timeout=30s;
}
location ~ \.php {
fastcgi_pass bakend;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME d:/www/htdocs$fastcgi_script_name;
include fastcgi_params;
}
這樣,埠9000與9001均分到了均等的處理機會,當一個程序(sever)繁忙時,由nginx調配另一個程序處理php程式(這裡nginx還有其它原理與實現)。
另一個問題是,手動讓系統管理多個php-cgi程式比較麻煩,特別是在遠端管理的時候尤為不方便,這時可以通過php-fpm或spawn-fcgi來代替,但在windows中並沒有php-fpm的二進位制編譯版本,只能在unix/linux中使用。具體檢視各自的手冊。
二是,看請求的程式是否一定需要動態解析,如果不是可以轉為html或js的格式。如果file_get_contents(‘http://127.0.0.1/a.html’)則不需要通過fastcgi來處理,也就不會出現阻塞的情況。
三是,php的程式取消交由fastcgi,這樣則使每個php請求都會自動開啟一個php程序,但不好的方面是php-cgi的優勢沒有了。
另外提醒下,網上有人說,通過去掉地址中的http://協議標記,而使用相對地址就規避函式的檢查,實際情況是不是這樣呢?!當在index.php中使用file_get_contents(‘phpinfo.php’);時,我們可以看到函式輸出了phpinfo.php的原始碼,相當於file_get_contents(‘file:\\\c:\www\phpinfo.php’);,它實際上只是讀取你的文字內容,因為file_get_contents()函式首先是處理file協議的,而curl則直接報錯無法解析。因此這些人純粹是不學無術的騙子。
還有人提出修改hosts檔案,增加localhost www.xxx.com影射關係,函式通過www.xxx.com訪問本地php,這其實也是不治本的偏方,因為這只是方便計算機的dns解析,最終www.xxx.com交給127.0.0.1,而後者交給fastcgi,還是阻塞。