fpassthru() 必须配合 fopen('rb') 才能安全输出二进制文件,因 'r' 模式在 Windows 下会误处理 \r\n 导致截断或乱码,'rb' 是跨平台安全底线;调用前须清缓冲、发正确 header(含 Content-Type 和 Content-Length)、检查返回值;非精细流控场景优先用 readfile()。

fpassthru 为什么必须配合 fopen('rb') 才能安全输出二进制文件
Windows 和 Linux 对换行符、EOF 判定的底层处理不同,fpassthru() 本身不转换数据,只原样转发指针当前位置到 EOF 的字节流。如果用 'r' 模式打开 PNG、PDF 或 ZIP 文件,在 Windows 上可能提前截断或出现乱码——因为 'r' 默认按文本模式解析 \r\n,会干扰二进制流完整性。
必须显式使用 'rb'(Windows)或 'r'(Linux/macOS 通常兼容,但加 b 更稳妥):
-
fopen($file, 'rb')是跨平台安全底线,PHP 官方文档明确要求二进制场景强制加b - 即使服务器是 Linux,也别省略
b:避免部署到 Windows 时突然失效 - 用
'r'打开文本文件(如 .txt、.log)一般没问题,但只要涉及图片、音频、压缩包,一律上'rb'
调用 fpassthru() 前忘记 header() 会怎样
直接输出二进制内容却没发 Content-Type 和 Content-Length,浏览器会当成 HTML 解析,显示乱码或空白,且无法触发下载。更糟的是,如果之前已有输出(比如空格、BOM、echo),PHP 会报 Warning: Cannot modify header information,fpassthru() 仍会执行但结果不可控。
正确顺序和必要检查:
立即学习“PHP免费学习笔记(深入)”;
- 确认输出缓冲未开启:
if (ob_get_level()) ob_end_clean();,否则header()失效 - 先发
header('Content-Type: ...'),类型必须匹配文件真实 MIME(如 PNG 用image/png,Excel 用application/vnd.openxmlformats-officedocument.spreadsheetml.sheet) - 必须发
Content-Length:用filesize($file),不是strlen(file_get_contents($file))(大文件会 OOM) - 最后才调
fpassthru($fp),之后立即exit;或die;,防止后续代码意外输出
fpassthru() 返回 false 的常见原因
它不抛异常,只返回 false 表示失败,但错误信息藏在 error_get_last() 里。常见踩坑点:
- 文件指针已关闭:
fclose($fp)后再调fpassthru($fp)→false - 文件被并发删除或权限变更:
fopen()成功但后续fpassthru()读取时文件消失 →false - 磁盘满或 I/O 错误:尤其在 NFS 或网络存储上,
fpassthru()可能中途断链 - 没检查返回值就直接
exit:应写成if (fpassthru($fp) === false) { error_log('fpassthru failed: ' . json_encode(error_get_last())); http_response_code(500); exit; }
什么时候该用 readfile() 而不是 fpassthru()
如果你只是把整个文件“原样吐出去”,没做任何前置读取(如 fgets())、没修改指针位置、也没用 fsockopen() 接流,readfile() 更简单安全:
-
readfile($path)内部自动处理fopen+fpassthru+fclose,少写三行,少一个资源泄漏风险 - 它自动适配二进制模式,无需手动指定
'rb',也不用担心指针位置 - 性能几乎无差别,但代码更短、可读性更高、出错路径更少
- 仅当需要精细控制指针(如跳过前 N 字节、分段传输、或接 HTTP 流)时,才选
fpassthru()
真正要用 fpassthru() 的场景其实很窄:你已经用 fopen() 或 fsockopen() 打开了资源,并且指针不在开头(比如读了一行日志后导出剩余内容),或者正在代理上游 HTTP 流。其他情况,readfile() 是更稳的选择。



















