PHP Conference Japan 2024

fread

(PHP 4, PHP 5, PHP 7, PHP 8)

fread二進位安全檔案讀取

說明

fread(資源 $stream, 整數 $length): 字串|false

fread()stream 參考的檔案指標讀取最多 length 個位元組。讀取會在滿足以下任一條件時停止:

  • 已讀取 length 個位元組
  • 到達 EOF(檔案結尾)
  • 收到封包或發生 通訊端逾時(適用於網路串流)
  • 如果串流是以緩衝方式讀取,且它並非代表一個普通的檔案,則最多只會讀取大小等於區塊大小(通常為 8192 位元組)的資料;根據先前已緩衝的資料,返回的資料大小可能大於區塊大小。

參數

stream

一個檔案系統指標資源,通常使用 fopen() 建立。

length

最多讀取 length 個位元組。

傳回值

傳回讀取的字串,或者在失敗時傳回 false

範例

範例 #1 簡單的 fread() 範例

<?php
// 將檔案內容讀入字串
$filename = "/usr/local/something.txt";
$handle = fopen($filename, "r");
$contents = fread($handle, filesize($filename));
fclose($handle);
?>

範例 #2 二進制 fread() 範例

警告

在區分二進制檔案和文字檔案的系統上(例如 Windows),檔案必須在 fopen() 模式參數中包含 'b' 來開啟。

<?php
$filename
= "c:\\files\\somepic.gif";
$handle = fopen($filename, "rb");
$contents = fread($handle, filesize($filename));
fclose($handle);
?>

範例 #3 遠端 fread() 範例

警告

當從非一般本地檔案的來源讀取資料時,例如讀取遠端檔案 或從 popen()fsockopen() 傳回的串流時,讀取動作會在收到一個封包後停止。這表示您應該像以下範例所示,將資料分塊收集。

<?php
$handle
= fopen("http://www.example.com/", "rb");
$contents = stream_get_contents($handle);
fclose($handle);
?>
<?php
$handle
= fopen("http://www.example.com/", "rb");
if (
FALSE === $handle) {
exit(
"Failed to open stream to URL");
}

$contents = '';

while (!
feof($handle)) {
$contents .= fread($handle, 8192);
}
fclose($handle);
?>

注意事項

備註:

如果您只是想將檔案內容讀入字串,請使用 file_get_contents(),因為它的效能比上面的程式碼好得多。

備註:

請注意,fread() 會從檔案指標的目前位置讀取。使用 ftell() 尋找指標的目前位置,並使用 rewind() 重設指標位置。

參見

  • fwrite() - 二進位制安全檔案寫入
  • fopen() - 開啟檔案或網址
  • fsockopen() - 開啟網際網路或 Unix 域套接字連線
  • popen() - 開啟程序檔案指標
  • fgets() - 從檔案指標取得一行
  • fgetss() - 從檔案指標取得一行並去除 HTML 標籤
  • fscanf() - 根據格式從檔案解析輸入
  • file() - 將整個檔案讀入陣列
  • fpassthru() - 輸出檔案指標上所有剩餘的資料
  • fseek() - 在檔案指標上搜尋
  • ftell() - 傳回檔案讀/寫指標的目前位置
  • rewind() - 重設檔案指標的位置
  • unpack() - 從二進位制字串解壓縮資料

新增注意事項

使用者貢獻的注意事項 14 則注意事項

dharmilshah at gmail dot com
10 年前
對於仍在嘗試編寫有效的文件下載函式/腳本的人來說,所有主流伺服器(包括 Apache 和 nginx)都已經幫你做好了。

使用 X-Sendfile 標頭,您可以執行以下操作

if ($user->isLoggedIn())
{
header("X-Sendfile: $path_to_somefile_private");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$somefile\"");
}

Apache 會幫你提供檔案,而且不會洩露你的私人檔案路徑!非常棒。這適用於所有瀏覽器/下載管理器,並節省大量資源。

文件
Apache 模組:https://tn123.org/mod_xsendfile/
Nginx:http://wiki.nginx.org/XSendfile
Lighttpd:http://blog.lighttpd.net/articles/2006/07/02/x-sendfile/

希望這可以為您節省許多工作時間。
edgarinvillegas at hotmail dot com
15 年前
我有一個 fread 腳本永遠掛起(來自 PHP 手冊)

<?php
$fp
= fsockopen("example.host.com", 80);
if (!
$fp) {
echo
"$errstr ($errno)<br />\n";
} else {
fwrite($fp, "Data sent by socket");
$content = "";
while (!
feof($fp)) { //這個迴圈會永遠執行下去
$content .= fread($fp, 1024);
}
fclose($fp);
echo
$content;
}
?>

問題是有時串流結束並非以 EOF 或固定標記標示,這就是為什麼這個迴圈會永遠執行下去。這讓我非常頭痛...
我使用 stream_get_meta_data 函式和 break 陳述式解決了這個問題,如下所示

<?php
$fp
= fsockopen("example.host.com", 80);
if (!
$fp) {
echo
"$errstr ($errno)<br />\n";
} else {
fwrite($fp, "Data sent by socket");
$content = "";
while (!
feof($fp)) {
$content .= fread($fp, 1024);
$stream_meta_data = stream_get_meta_data($fp); //新增一行
if($stream_meta_data['unread_bytes'] <= 0) break; //新增一行
}
fclose($fp);
echo
$content;
}
?>

希望這可以幫其他人省去很多麻煩。

(來自玻利維亞拉巴斯 的問候)
mail at 3v1n0 dot net
16 年前
這是我為下載具有 HTTP 續傳支援的遠端檔案所做的修改。如果您想編寫一個可以從遠端擷取檔案然後將其發送給使用者,並加入下載管理器支援的下載腳本,這會很有用(我已在 wget 上測試過)。要做到這一點,您還應該使用一個您可以輕鬆編寫/找到的「remote_filesize」函式。

<?php
function readfile_chunked_remote($filename, $seek = 0, $retbytes = true, $timeout = 3) {
set_time_limit(0);
$defaultchunksize = 1024*1024;
$chunksize = $defaultchunksize;
$buffer = '';
$cnt = 0;
$remotereadfile = false;

if (
preg_match('/[a-zA-Z]+:\/\//', $filename))
$remotereadfile = true;

$handle = @fopen($filename, 'rb');

if (
$handle === false) {
return
false;
}

stream_set_timeout($handle, $timeout);

if (
$seek != 0 && !$remotereadfile)
fseek($handle, $seek);

while (!
feof($handle)) {

if (
$remotereadfile && $seek != 0 && $cnt+$chunksize > $seek)
$chunksize = $seek-$cnt;
else
$chunksize = $defaultchunksize;

$buffer = @fread($handle, $chunksize);

if (
$retbytes || ($remotereadfile && $seek != 0)) {
$cnt += strlen($buffer);
}

if (!
$remotereadfile || ($remotereadfile && $cnt > $seek))
echo
$buffer;

ob_flush();
flush();
}

$info = stream_get_meta_data($handle);

$status = fclose($handle);

if (
$info['timed_out'])
return
false;

if (
$retbytes && $status) {
return
$cnt;
}

return
$status;
}
?>
Edward Jaramilla
16 年前
我無法讓之前的一些續傳腳本與 Free Download Manager 或 Firefox 一起使用。我做了一些清理並稍微修改了程式碼。

變更
1. 新增了一個旗標來指定是否要讓下載可以續傳
2. 根據 http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt 進行了一些錯誤檢查和無效/多個範圍的資料清理
3. 即使範圍規範說它可以是空的,也要計算 $seek_end ... 例如:bytes 500-/1234
4. 刪除了一些似乎不需要的快取標頭。(如果出現問題,請加回去)
5. 僅在下載檔案的一部分時才發送部分內容標頭(IE 的解決方法)

<?php

function dl_file_resumable($file, $is_resume=TRUE)
{
//First, see if the file exists
if (!is_file($file))
{
die(
"<b>404 File not found!</b>");
}

//Gather relevent info about file
$size = filesize($file);
$fileinfo = pathinfo($file);

//workaround for IE filename bug with multiple periods / multiple dots in filename
//that adds square brackets to filename - eg. setup.abc.exe becomes setup[1].abc.exe
$filename = (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) ?
preg_replace('/\./', '%2e', $fileinfo['basename'], substr_count($fileinfo['basename'], '.') - 1) :
$fileinfo['basename'];

$file_extension = strtolower($path_info['extension']);

//This will set the Content-Type to the appropriate setting for the file
switch($file_extension)
{
case
'exe': $ctype='application/octet-stream'; break;
case
'zip': $ctype='application/zip'; break;
case
'mp3': $ctype='audio/mpeg'; break;
case
'mpg': $ctype='video/mpeg'; break;
case
'avi': $ctype='video/x-msvideo'; break;
default:
$ctype='application/force-download';
}

//check if http_range is sent by browser (or download manager)
if($is_resume && isset($_SERVER['HTTP_RANGE']))
{
list(
$size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);

if (
$size_unit == 'bytes')
{
//multiple ranges could be specified at the same time, but for simplicity only serve the first range
//http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
list($range, $extra_ranges) = explode(',', $range_orig, 2);
}
else
{
$range = '';
}
}
else
{
$range = '';
}

//figure out download piece from range (if set)
list($seek_start, $seek_end) = explode('-', $range, 2);

//set start and end based on range (if set), else set defaults
//also check for invalid ranges.
$seek_end = (empty($seek_end)) ? ($size - 1) : min(abs(intval($seek_end)),($size - 1));
$seek_start = (empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)),0);

//add headers if resumable
if ($is_resume)
{
//Only send partial content header if downloading a piece of the file (IE workaround)
if ($seek_start > 0 || $seek_end < ($size - 1))
{
header('HTTP/1.1 206 Partial Content');
}

header('Accept-Ranges: bytes');
header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$size);
}

//headers for IE Bugs (is this necessary?)
//header("Cache-Control: cache, must-revalidate");
//header("Pragma: public");

header('Content-Type: ' . $ctype);
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: '.($seek_end - $seek_start + 1));

//open the file
$fp = fopen($file, 'rb');
//seek to start of missing part
fseek($fp, $seek_start);

//start buffered download
while(!feof($fp))
{
//reset time limit for big files
set_time_limit(0);
print(
fread($fp, 1024*8));
flush();
ob_flush();
}

fclose($fp);
exit;
}

?>
john dot wellesz at teaser dot com
10 年前
請注意,當發生逾時時,fread() 會返回 ''(空字串),不像 socket_read() 會返回 false ...
matt at matt-darby dot com
17 年前
我以為我遇到了 fread() 在處理大於 30M 的檔案時會失敗的問題。我嘗試了 file_get_contents() 方法,結果相同。問題不在於讀取檔案,而在於將其資料回傳給瀏覽器。

基本上,您需要在將檔案資料發送到瀏覽器之前,將其分割成可管理的區塊

<?php

$total
= filesize($filepath);
$blocksize = (2 << 20); //2MB 的區塊
$sent = 0;
$handle = fopen($filepath, "r");

// 設定標頭,告知瀏覽器下載檔案的類型
header('Content-type: '.$content_type);
header('Content-Disposition: attachment; filename='.$filename);
header('Content-length: '.$filesize * 1024);

// 必須迴圈讀取檔案並輸出區塊資料
// 直接輸出整個檔案在超過 30MB 時會失敗!
while($sent < $total){
echo
fread($handle, $blocksize);
$sent += $blocksize;
}

exit(
0);

?>

希望這對某些人有幫助!
randym
13 年前
關於 [UTF-8 的問題以及] 下載 Zip 檔案,我發現只要在開始使用 fread 將資料讀取到緩衝區傳送之前,加入以下三行程式碼,就可以解決所有瀏覽器的問題。

<?php
ob_end_clean
();
ob_start();
header( 'Content-Type:' );
?>

... 請參考下方函式中的位置

<?php
函數 readfile_chunked( $filename, $retbytes = true ) {
$chunksize = 1 * (1024 * 1024); // 每個區塊的位元組數
$buffer = '';
$cnt = 0;
$handle = fopen( $filename, 'rb' );
if (
$handle === false ) {
return
false;
}
ob_end_clean(); // 加入此行以修正 ZIP 檔案損毀問題
ob_start(); // 加入此行以修正 ZIP 檔案損毀問題
header( 'Content-Type:' ); // 加入此行以修正 ZIP 檔案損毀問題
while ( !feof( $handle ) ) {
$buffer = fread( $handle, $chunksize );
//$buffer = str_replace("","",$buffer);
echo $buffer;
ob_flush();
flush();
if (
$retbytes ) {
$cnt += strlen( $buffer );
}
}
$status = fclose( $handle );
if (
$retbytes && $status ) {
return
$cnt; // 返回已傳送的位元組數,如同 readfile() 的行為。
}
return
$status;
}
?>
匿名
16 年前
值得注意的是,如果您的網站使用帶有工作階段的前端控制器,並且您要將一個大型檔案傳送給使用者;您應該在傳送檔案之前結束工作階段,否則使用者在檔案下載時將無法繼續瀏覽網站。
shocker at shockingsoft dot com
16 年前
如果您從 socket 連線或任何其他可能延遲回應的串流讀取,但您想設定逾時,則可以使用 stream_set_timeout()

<?php
$f
= fsockopen("127.0.0.1", 123);
if (
$f)
{
fwrite($f, "hello");
stream_set_timeout($f, 5); //設定 5 秒讀取逾時
if (!fread($f, 5)) echo "Error while reading";
else echo
"Read ok";
fclose($f);
}
?>
匿名
13 年前
如果您使用 PHP 的 fread 和 print/echo 提供檔案下載,並且遇到二進位檔案損壞的情況,則可能是伺服器仍在使用魔術引號 (magic quotes) 並跳脫檔案中的空位元組。雖然從 5.3.0 版本開始不再支援魔術引號,但您可能仍然會遇到這個問題。請嘗試在使用 fread 之前放置以下程式碼來關閉魔術引號:

<?php
@ini_set('magic_quotes_runtime', 0);
?>
mrhappy[at]dotgeek.org
19 年前
給任何嘗試實作 PHP 檔案下載腳本的人一點提醒:

我們花了很多時間試圖找出為什麼我們的程式碼在處理大型檔案時會耗盡系統資源。最終,我們設法追蹤到問題的根源,是每個頁面都透過 include 啟動的輸出緩衝區。(它試圖在將資料發送到用戶端*之前*緩衝整個 600 MB 或任何大小的檔案)如果您遇到這個問題,您可能需要先檢查一下,並停止啟動緩衝區或以一般方式關閉它 :)

希望這可以防止有人花費數小時來解決一個難以理解的問題。

此致 :)
Richard Dale richard at premiumdata dot n dot e dot t
19 年前
如果您使用上述任何程式碼下載檔案,如果檔名中有多個句點,Internet Explorer 會將檔名更改為包含方括號的檔名。為了避免這個問題,我們會檢查使用者代理程式是否包含 MSIE,並將必要的句點改寫為 %2E。

<?php
# 例如: $filename="setup.abc.exe";
if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) {
# 針對 IE 檔名有多個點號時的錯誤的解決方案
# IE 會在檔名加上方括號 - 例如: setup.abc.exe 會變成 setup[1].abc.exe
$iefilename = preg_replace('/\./', '%2e', $filename, substr_count($filename, '.') - 1);
header("Content-Disposition: attachment; filename=$iefilename" );
} else {
header("Content-Disposition: attachment; filename=$filename");
}
?>
m (at) mindplay (dot) dk
19 年前
這是一個將檔案傳送給用戶端的函式 - 它看起來可能比需要的更複雜,但與較簡單的檔案傳送函式相比,它有許多優點

- 適用於大型檔案,每次傳輸僅使用 8KB 緩衝區。

- 如果用戶端斷線,則停止傳輸(不像許多腳本,會繼續讀取和緩衝整個檔案,浪費寶貴的資源),但不會停止腳本

- 如果傳輸完成,則返回 TRUE,如果在完成下載之前用戶端斷線,則返回 FALSE - 您通常需要這樣做,以便正確記錄下載。

- 傳送許多標頭,包括確保在任何瀏覽器/代理上最多快取 2 小時的標頭,以及大多數人似乎忘記的「Content-Length」。

(在 PHP4.3.x 下的 Linux (Apache) 和 Windows (IIS5/6) 上測試)

請注意,將提取受保護檔案的資料夾在此函式中設定為常數 (/protected) ... 現在以下是該函式

<?php
function send_file($name) {
ob_end_clean();
$path = "protected/".$name;
if (!
is_file($path) || connection_status()!=0) return false;
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Expires: ".gmdate("D, d M Y H:i:s", mktime(date("H")+2, date("i"), date("s"), date("m"), date("d"), date("Y")))." GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Content-Type: application/octet-stream");
header("Content-Length: ".(string)(filesize($path)));
header("Content-Disposition: inline; filename=$name");
header("Content-Transfer-Encoding: binary\n");
if (
$file = fopen($path, 'rb')) {
while(!
feof($file) and (connection_status()==0)) {
print(
fread($file, 1024*8));
flush();
}
fclose($file);
}
return (
connection_status()==0) && !connection_aborted();
}
?>

以下是使用這個函式的範例:

<?php
if (!send_file("platinumdemo.zip")) {
die (
"檔案傳輸失敗");
// 檔案傳輸未完成
// 或找不到檔案
} else {
// 下載成功
// 記錄或執行其他操作
}
?>

此致,
Rasmus Schultz
admin at codebydeer dot com
2 個月前
你需要知道,如果 fread() 的 $length 參數為 0,將會導致致命錯誤 (Fatal Error)。
<?php
$filepath
= "example/a/dir/file.txt";

$fp = fopen($filepath, "c+");
fseek($fp, 0);
$fileSize = fstat($fp)["size"];
if (
$fileSize <= 0) {
$txt = "";
} else {
$txt = fread($fp, $fileSize);
}
echo
$txt;
?>
我是日本人,如果我犯了一些錯誤,非常抱歉。
謝謝!
To Top