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(
"无法打开到 URL 的流");
}

$contents = '';

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

注释

注意:

如果您只想将文件内容获取到字符串中,请使用 file_get_contents(),因为它比上述代码的性能要好得多。

注意:

请注意,fread() 从文件指针的当前位置读取。使用 ftell() 查找指针的当前位置,并使用 rewind() 重置指针位置。

参见

  • fwrite() - 二进制安全文件写入
  • fopen() - 打开文件或 URL
  • fsockopen() - 打开 Internet 或 Unix 域套接字连接
  • popen() - 打开进程文件指针
  • fgets() - 从文件指针获取行
  • fgetss() - 从文件指针获取行并去除 HTML 标签
  • fscanf() - 根据格式解析来自文件的内容
  • file() - 读取整个文件到数组中
  • fpassthru() - 输出文件指针上所有剩余数据
  • fseek() - 在文件指针上查找
  • ftell() - 返回文件读/写指针的当前位置
  • rewind() - 重置文件指针的位置
  • unpack() - 从二进制字符串解包数据

添加注释

用户贡献的注释 14 条注释

20
dharmilshah at gmail dot com
10 年前
对于任何仍然尝试编写有效的有效文件下载器函数/脚本的人来说,这项工作已经为您在所有主要的服务器(包括 Apache 和 nginx)中完成。

使用 X-Sendfile 头,您可以执行以下操作

如果 ($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/

希望这能为您节省大量工作时间。
25
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;
}
?>

希望这能为某些人节省很多麻烦。

(来自玻利维亚拉巴斯的问候)
11
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;
}
?>
24
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)
{
//首先,查看文件是否存在
if (!is_file($file))
{
die(
"<b>404 文件未找到!</b>");
}

//收集相关文件信息
$size = filesize($file);
$fileinfo = pathinfo($file);

//解决IE文件名bug,文件名中包含多个点时,IE会添加方括号 - 例如:setup.abc.exe 变为 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']);

//设置Content-Type为对应文件类型
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';
}

//检查浏览器(或下载管理器)是否发送了http_range
if($is_resume && isset($_SERVER['HTTP_RANGE']))
{
list(
$size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);

if (
$size_unit == 'bytes')
{
//可以同时指定多个范围,但为了简单起见,只处理第一个范围
//http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
list($range, $extra_ranges) = explode(',', $range_orig, 2);
}
else
{
$range = '';
}
}
else
{
$range = '';
}

//根据范围(如果设置)计算下载片段
list($seek_start, $seek_end) = explode('-', $range, 2);

//根据范围(如果设置)设置起始和结束位置,否则设置默认值
//同时检查无效范围。
$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);

//如果可恢复,则添加头部
if ($is_resume)
{
//仅在下载文件的一部分时发送部分内容头部(IE解决方法)
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);
}

//IE Bug 的头部(这有必要吗?)
//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));

//打开文件
$fp = fopen($file, 'rb');
//跳转到缺失部分的开头
fseek($fp, $seek_start);

//开始缓冲下载
while(!feof($fp))
{
//重置大文件的超时限制
set_time_limit(0);
print(
fread($fp, 1024*8));
flush();
ob_flush();
}

fclose($fp);
exit;
}

?>
7
john dot wellesz at teaser dot com
10 年前
请注意,当发生超时时,fread() 将返回 ''(空字符串),而 socket_read() 返回 false...
9
matt at matt-darby dot com
17 年前
我以为我遇到了 fread() 在文件大小超过 30M 时会失败的问题。我尝试了 file_get_contents() 方法,结果相同。问题不在于读取文件,而在于将数据回显到浏览器。

基本上,你需要将文件数据分成可管理的块,然后再发送到浏览器。

<?php

$total
= filesize($filepath);
$blocksize = (2 << 20); //2M 块
$sent = 0;
$handle = fopen($filepath, "r");

// 推送头部,告知浏览器文件类型
header('Content-type: '.$content_type);
header('Content-Disposition: attachment; filename='.$filename);
header('Content-length: '.$filesize * 1024);

// 现在我们需要循环遍历文件并回显文件数据的块
// 一次性转储整个文件在 > 30M 时会失败!
while($sent < $total){
echo
fread($handle, $blocksize);
$sent += $blocksize;
}

exit(
0);

?>

希望这对某些人有所帮助!
8
randym
13 年前
关于 [UTF-8 问题和] 下载 Zip 文件,我发现只需在开始将 fread 缓冲到所有浏览器之前添加 3 行代码即可解决此问题。

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

... 请参阅下面函数中的放置位置

<?php
function 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;
}
?>
7
匿名用户
16 年前
值得注意的是,如果您的网站使用带有会话的前端控制器,并且您向用户发送了一个大文件;您应该在发送文件之前结束会话,否则用户将无法在文件下载期间继续浏览网站。
3
[email protected]
16 年前
如果您从套接字连接或任何其他可能延迟响应的流中读取,但您希望设置超时,则可以使用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 "读取错误";
else echo
"读取成功";
fclose($f);
}
?>
4
匿名用户
13 年前
如果您使用fread和print/echo通过PHP提供文件下载,并遇到二进制文件损坏的情况,则可能是服务器仍在使用magic quotes并转义文件中的空字节。虽然从5.3.0开始不再支持magic quotes,但您仍然可能遇到此问题。尝试通过在使用fread之前放置以下代码来关闭它们

<?php
@ini_set('magic_quotes_runtime', 0);
?>
2
[email protected]
19年前
对于任何尝试实现php处理的下载脚本的人来说,这是一个提示 -

我们花了很长时间试图找出为什么我们的代码在处理大文件时会消耗系统资源.. 最终我们设法追踪到输出缓冲,它通过包含文件在每个页面上启动..(它试图在发送数据到客户端*之前*缓冲整个600兆或任何大小)如果您遇到此问题,您可能需要首先检查它,并且要么不启动缓冲,要么以通常的方式关闭它 :)

希望这可以防止有人花费数小时试图解决一个模糊的问题。

致礼 :)
1
Richard Dale [email protected]
19年前
如果您使用上述任何代码下载文件,Internet Explorer 将在文件名中包含多个句点时将其更改为带有方括号的文件名。为了解决此问题,我们检查 User Agent 是否包含 MSIE 并将必要的句点重写为 %2E

<?php
# 例如:$filename="setup.abc.exe";
if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) {
// 解决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");
}
?>
3
[email protected]
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) or 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) and !connection_aborted());
}
?>

下面是一个使用该函数的例子

<?php
if (!send_file("platinumdemo.zip")) {
die (
"文件传输失败");
// 文件传输不完整或文件未找到
// 或文件未找到
} else {
// 下载成功
// 记录日志或执行其他操作
}
?>

此致,
Rasmus Schultz
0
admin at codebydeer dot com
2 个月前
您需要知道,如果 fread() 的 $length 参数为 0,则会发生致命错误。
<?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