PHP fpassthru()函数
fpassthru()函数用于输出文件指针处的所有剩余数据,通常用于输出二进制文件(如图片、PDF、音频等),并将文件指针移动到文件末尾。
语法
int fpassthru ( resource $handle )
参数说明
| 参数 |
描述 |
handle |
文件指针资源,通常由fopen()函数打开,并且必须以二进制模式(如'rb')打开 |
返回值
- 成功时返回读取并输出的字节数
- 失败时返回
false
- 调用后,文件指针将移动到文件末尾(或到达EOF)
示例代码
示例1:输出图片文件
<?php
// 输出图片文件
$imagePath = 'photo.jpg';
// 检查文件是否存在
if (!file_exists($imagePath)) {
die('图片文件不存在');
}
// 以二进制只读模式打开文件
$fp = fopen($imagePath, 'rb');
if ($fp) {
// 设置正确的Content-Type头部
header('Content-Type: image/jpeg');
// 输出文件内容
fpassthru($fp);
// 关闭文件指针
fclose($fp);
exit; // 终止脚本执行,避免输出额外内容
} else {
echo '无法打开图片文件';
}
?>
示例2:输出PDF文档
<?php
// 输出PDF文件
function outputPdfFile($filename) {
if (!file_exists($filename)) {
header('HTTP/1.1 404 Not Found');
echo '文件不存在';
return false;
}
// 获取文件信息
$fileSize = filesize($filename);
$modifiedTime = filemtime($filename);
// 以二进制模式打开文件
$fp = fopen($filename, 'rb');
if (!$fp) {
header('HTTP/1.1 500 Internal Server Error');
echo '无法打开文件';
return false;
}
// 设置PDF文件的HTTP头部
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . basename($filename) . '"');
header('Content-Length: ' . $fileSize);
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $modifiedTime) . ' GMT');
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: public');
// 输出文件内容
$bytes = fpassthru($fp);
fclose($fp);
return $bytes;
}
// 使用示例
if (isset($_GET['pdf'])) {
outputPdfFile('document.pdf');
exit;
}
?>
示例3:大文件下载功能
<?php
// 安全的大文件下载函数
function downloadFile($filePath, $downloadName = null) {
// 安全检查
if (!file_exists($filePath)) {
return '文件不存在';
}
if (!is_readable($filePath)) {
return '文件不可读';
}
// 防止目录遍历攻击
$realPath = realpath($filePath);
$baseDir = realpath('./downloads'); // 只允许下载downloads目录下的文件
if (strpos($realPath, $baseDir) !== 0) {
return '非法文件路径';
}
// 设置下载文件名
if ($downloadName === null) {
$downloadName = basename($filePath);
}
// 获取文件信息
$fileSize = filesize($filePath);
$fileExt = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
// 根据文件类型设置Content-Type
$mimeTypes = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'pdf' => 'application/pdf',
'zip' => 'application/zip',
'txt' => 'text/plain',
'csv' => 'text/csv',
];
$contentType = $mimeTypes[$fileExt] ?? 'application/octet-stream';
// 设置HTTP头部
header('Content-Type: ' . $contentType);
header('Content-Disposition: attachment; filename="' . rawurlencode($downloadName) . '"');
header('Content-Length: ' . $fileSize);
header('Cache-Control: no-cache, must-revalidate');
header('Expires: 0');
// 打开文件并输出
$fp = fopen($filePath, 'rb');
if ($fp) {
fpassthru($fp);
fclose($fp);
return true;
}
return false;
}
// 使用示例
if (isset($_GET['file'])) {
$file = $_GET['file'];
downloadFile('downloads/' . $file);
exit;
}
?>
示例4:与readfile()函数的对比
<?php
// fpassthru()和readfile()的功能对比
$filename = 'example.txt';
echo "使用fpassthru()输出文件内容:
";
// 使用fpassthru()需要先打开文件
$fp = fopen($filename, 'rb');
if ($fp) {
// 获取当前输出缓冲内容
ob_start();
$bytes1 = fpassthru($fp);
$output1 = ob_get_clean();
fclose($fp);
echo "输出字节数: " . $bytes1 . "<br>";
echo "内容预览: " . htmlspecialchars(substr($output1, 0, 100)) . "...<br><br>";
}
echo "使用readfile()输出文件内容:
";
// readfile()更简单,不需要显式打开文件
$bytes2 = readfile($filename);
echo "<br>输出字节数: " . $bytes2 . "<br>";
echo "性能对比测试:
";
$testFile = 'large_file.bin'; // 假设有一个大文件
if (file_exists($testFile)) {
// 测试fpassthru()
$start1 = microtime(true);
$fp = fopen($testFile, 'rb');
fpassthru($fp);
fclose($fp);
$time1 = microtime(true) - $start1;
// 测试readfile()
$start2 = microtime(true);
readfile($testFile);
$time2 = microtime(true) - $start2;
echo "fpassthru()耗时: " . number_format($time1, 4) . " 秒<br>";
echo "readfile()耗时: " . number_format($time2, 4) . " 秒<br>";
}
?>
示例5:输出视频文件(支持断点续传)
<?php
// 支持HTTP范围请求(断点续传)的视频输出
function streamVideo($filePath) {
if (!file_exists($filePath)) {
header('HTTP/1.1 404 Not Found');
return;
}
$fileSize = filesize($filePath);
$fileName = basename($filePath);
$fileExt = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
// 视频文件的MIME类型
$videoTypes = [
'mp4' => 'video/mp4',
'webm' => 'video/webm',
'ogg' => 'video/ogg',
'avi' => 'video/x-msvideo',
'mov' => 'video/quicktime',
];
$contentType = $videoTypes[$fileExt] ?? 'application/octet-stream';
// 处理HTTP范围请求
$range = null;
if (isset($_SERVER['HTTP_RANGE'])) {
preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
$start = (int)$matches[1];
$end = isset($matches[2]) ? (int)$matches[2] : $fileSize - 1;
$range = [$start, $end];
}
// 打开文件
$fp = fopen($filePath, 'rb');
if (!$fp) {
header('HTTP/1.1 500 Internal Server Error');
return;
}
if ($range) {
// 部分内容响应
header('HTTP/1.1 206 Partial Content');
header('Content-Type: ' . $contentType);
header('Content-Range: bytes ' . $range[0] . '-' . $range[1] . '/' . $fileSize);
header('Content-Length: ' . ($range[1] - $range[0] + 1));
header('Accept-Ranges: bytes');
// 跳转到指定位置
fseek($fp, $range[0]);
// 输出指定范围的内容
$length = $range[1] - $range[0] + 1;
$bytes = 0;
while ($bytes < $length && !feof($fp)) {
$chunk = min($length - $bytes, 8192); // 8KB块
echo fread($fp, $chunk);
$bytes += $chunk;
flush(); // 刷新输出缓冲
}
} else {
// 完整文件响应
header('Content-Type: ' . $contentType);
header('Content-Length: ' . $fileSize);
header('Accept-Ranges: bytes');
// 使用fpassthru输出整个文件
fpassthru($fp);
}
fclose($fp);
}
// 使用示例
if (isset($_GET['video'])) {
streamVideo('videos/sample.mp4');
exit;
}
?>
注意事项
重要提示:
- 二进制模式:必须使用二进制模式(如'rb')打开文件,否则在Windows系统上可能无法正确处理二进制文件
- HTTP头部:在调用fpassthru()之前,必须先发送正确的Content-Type头部,否则浏览器可能无法正确解析内容
- 输出缓冲:如果启用了输出缓冲,fpassthru()可能无法立即输出内容,需要使用
ob_end_flush()或flush()
- 内存效率:fpassthru()非常适合输出大文件,因为它不会将整个文件加载到内存中
- 文件指针位置:调用fpassthru()后,文件指针会移动到文件末尾,后续读取将返回EOF
- 错误处理:fpassthru()失败时返回false,但通常会在失败时输出部分数据
- readfile()替代:对于简单的文件输出,
readfile()是更简单的选择,不需要显式打开和关闭文件
- 安全性:确保用户无法通过路径遍历访问系统文件,需要对文件路径进行严格验证
fpassthru() vs readfile()
| 特性 |
fpassthru() |
readfile() |
| 用法 |
需要先使用fopen()打开文件 |
直接传入文件名 |
| 灵活性 |
可以在输出前对文件指针进行操作(如fseek()) |
直接输出整个文件 |
| 性能 |
稍慢(需要两次函数调用) |
稍快(单次函数调用) |
| 内存使用 |
流式输出,内存效率高 |
流式输出,内存效率高 |
| 适用场景 |
需要操作文件指针的场景(如断点续传) |
简单的文件下载/输出 |
相关函数
readfile() - 输出文件内容(不需要显式打开文件)
fopen() - 打开文件或URL
fread() - 读取文件内容
fclose() - 关闭一个已打开的文件指针
fseek() - 在文件指针中定位
ftell() - 返回文件指针读/写的位置
header() - 发送原始HTTP头部
filesize() - 获取文件大小
典型应用场景
- 图片输出:动态输出图片文件(如验证码、用户上传的图片)
- 文件下载:实现文件下载功能,支持大文件
- PDF预览:在浏览器中直接显示PDF文档
- 视频流:输出视频文件,支持断点续传
- 音频播放:输出音频文件供在线播放
- 二进制文件:输出ZIP、EXE等二进制文件
- 动态内容生成:结合GD库生成动态图片并输出