一、普通文件下載
①laravel框架HTTP響應(yīng)的download方法
$pathToFile = 'myfile.csv';//參數(shù)一:絕對路徑
$downloadName = 'downloadFile.csv';//參數(shù)二:下載后的文件名
//download 參數(shù)三:HTTP頭信息
return response()->download($pathToFile, $downloadName);
②PHP實現(xiàn)
$pathToFile = 'myfile.csv';//文件絕對路徑
$downloadName = 'downloadFile.csv';//下載后的文件名
//輸入文件標(biāo)簽
Header("Content-type: application/octet-stream");
Header("Accept-Ranges: bytes");
Header("Accept-Length: " . filesize($pathToFile));
Header("Content-Disposition: filename=" . $downloadName);
//輸出文件內(nèi)容
$file = fopen($pathToFile, "r");
echo fread($file, filesize($pathToFile));
fclose($file);
//或
//readfile($pathToFile);
其中fread()與readfile()的區(qū)別可以參考https://segmentfault.com/q/10...
但是有時候為了節(jié)省帶寬售貌,避免瞬時流量過大而造成網(wǎng)絡(luò)堵塞给猾,就要考慮下載限速的問題
二、下載文件限速
$pathToFile = 'myfile.csv';//文件絕對路徑
$downloadName = 'downloadFile.csv';//下載后的文件名
$download_rate = 30;// 設(shè)置下載速率(30 kb/s)
if (file_exists($pathToFile) && is_file($pathToFile)) {
header('Cache-control: private');// 發(fā)送 headers
header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($pathToFile));
header('Content-Disposition: filename=' . $downloadName);
flush();// 刷新內(nèi)容
$file = fopen($pathToFile, "r");
while (!feof($file)) {
print fread($file, round($download_rate * 1024));// 發(fā)送當(dāng)前部分文件給瀏覽者
flush();// flush 內(nèi)容輸出到瀏覽器端
sleep(1);// 終端1秒后繼續(xù)
}
fclose($file);// 關(guān)閉文件流
} else {
abort(500, '文件' . $pathToFile . '不存在');
}
此時出現(xiàn)一個問題颂跨,當(dāng)$download_rate>1kb時敢伸,文件正常下載;當(dāng)$download_rate<1kb時恒削,文件要等一會兒才下載池颈,究其原因是因為buffer的問題。
- buffer是一個內(nèi)存地址空間,Linux系統(tǒng)默認(rèn)大小一般為4096(1kb),即一個內(nèi)存頁钓丰。主要用于存儲速度不同步的設(shè)備或者優(yōu)先級不同的設(shè)備之間傳辦理數(shù)據(jù)的區(qū)域躯砰。舉個例子,你打開文本編輯器編輯一個文件的時候携丁,你每輸入一個字符琢歇,操作系統(tǒng)并不會立即把這個字符直接寫入到磁盤,而是先寫入到buffer梦鉴,當(dāng)寫滿了一個buffer的時候李茫,才會把buffer中的數(shù)據(jù)寫入磁盤。同樣的道理肥橙,當(dāng)執(zhí)行echo,print的時候魄宏,輸出并沒有立即通過tcp傳給客戶端瀏覽器顯示,而是將數(shù)據(jù)寫入php buffer。php output_buffering機制快骗,意味在tcp buffer之前娜庇,建立了一新的隊列,數(shù)據(jù)必須經(jīng)過該隊列方篮。當(dāng)一個php buffer寫滿的時候名秀,腳本進程會將php buffer中的輸出數(shù)據(jù)交給系統(tǒng)內(nèi)核交由tcp傳給瀏覽器顯示。所以藕溅,數(shù)據(jù)會依次寫到這幾個地方echo/pring -> php buffer -> tcp buffer -> browser匕得。資料:http://blog.csdn.net/superhos...
- 在沒有開啟緩存時,腳本輸出的內(nèi)容都在服務(wù)器端處于等待輸出的狀態(tài),flush()可以將等待輸出的內(nèi)容立即發(fā)送到客戶端汁掠。
- 開啟緩存后略吨,腳本輸出的內(nèi)容存入了輸出緩存中,這時沒有處于等待輸出狀態(tài)的內(nèi)容考阱,你直接使用flush()不會向客戶端發(fā)出任何內(nèi)容翠忠。而ob_flush()的作用就是將本來存在輸出緩存中的內(nèi)容取出來,設(shè)置為等待輸出狀態(tài)乞榨,但不會直接發(fā)送到客戶端秽之,這時你就需要先使用ob_flush()再使用flush(),客戶端才能立即獲得腳本的輸出吃既。
- 以及這篇文章同樣講述了ob_flush()和flush()的區(qū)別http://www.laruence.com/2010/...
但是這種方法將文件內(nèi)容從磁盤經(jīng)過一個固定的 buffer 去循環(huán)讀取到內(nèi)存考榨,再發(fā)送給前端 web 服務(wù)器,最后才到達(dá)用戶鹦倚。當(dāng)需要下載的文件很大的時候河质,這種方式將消耗大量內(nèi)存,甚至引發(fā) php 進程超時或崩潰震叙,接下來就使用到X-Sendfile掀鹅。
三、X-Sendfile
- X-Sendfile 是一種將文件下載請求由后端應(yīng)用轉(zhuǎn)交給前端 web
服務(wù)器處理的機制媒楼,它可以消除后端程序既要讀文件又要處理發(fā)送的壓力淫半,從而顯著提高服務(wù)器效率,特別是處理大文件下載的情形下匣砖。
我是用的nginx,所以apache請參考https://tn123.org/mod_xsendfile/
①首先在配置文件中添加
location /download/ {
internal;
root /some/path;//絕對路徑
}
- internal 表示這個路徑只能在 Nginx 內(nèi)部訪問昏滴,不能用瀏覽器直接訪問防止未授權(quán)的下載
- 注意添加在location / {...}的前面
- 這樣你在代碼中使用時猴鲫,文件路徑就可以寫成“/download/myfile.csv”
②重啟Nginx,寫代碼
$pathToFile = 'myfile.csv';//文件絕對路徑
$downloadName = 'downloadFile.csv';//下載后的文件名
$download_rate = 30;// 設(shè)置下載速率(30 kb/s)
if (file_exists($pathToFile) && is_file($pathToFile)) {
return (new Response())->withHeaders([
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment;filename=' . $downloadName,
'X-Accel-Redirect' => $pathToFile,//讓Xsendfile發(fā)送文件
'X-Sendfile' => $pathToFile,
'X-Accel-Limit-Rate' => $download_rate,
]);
}else {
abort(500, '文件' . $pathToFile . '不存在');
}
如果你還想了解更多關(guān)于X-sendfile,請自行查閱