使用 readfile() 配合正确的 http 响应头即可实现 php 强制下载 mp3 文件。核心做法是设置 content-type 为 audio/mpeg,将 content-disposition 设为 attachment 并指定文件名,最后通过 readfile() 读取文件流输出到浏览器。

readfile() 如何触发浏览器下载行为
readfile() 函数负责将文件内容直接写入输出缓冲区,而下载行为的触发完全依赖于之前设置的 Content-Disposition: attachment 头部。如果不设置该头部,浏览器会尝试直接播放音频;设置为 attachment 后,浏览器会拦截播放请求并弹出保存对话框。同时必须确保在发送任何 HTML 或空白字符之前调用 header(),否则会导致头部发送失败,进而使下载功能失效。

<?php
$file = 'sample.mp3';
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: audio/mpeg');
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
}上述代码首先检查文件是否存在,随后依次设置描述、类型、处置方式和长度。执行后,浏览器会提示用户保存名为 sample.mp3 的文件。若文件路径错误或权限不足,readfile() 会返回 false 并产生警告,但不会输出文件内容。
立即学习“PHP免费学习笔记(深入)”;
如何处理大文件下载的内存与超时问题
对于体积较大的 MP3 文件,直接使用 readfile() 可能导致服务器内存溢出或脚本执行超时。此时应改用分块读取的方式,结合 fopen() 和 fread() 循环输出数据。这种方式不一次性加载整个文件到内存,而是每次读取固定字节数(如 8192 字节)并立即输出,从而保持内存占用恒定。同时需要调用 set_time_limit(0) 解除 PHP 的执行时间限制,确保长耗时下载能顺利完成。

<?php
$file = 'large_audio.mp3';
if (file_exists($file)) {
set_time_limit(0);
header('Content-Description: File Transfer');
header('Content-Type: audio/mpeg');
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
header('Content-Length: ' . filesize($file));
$handle = fopen($file, 'rb');
if ($handle) {
while (!feof($handle)) {
echo fread($handle, 8192);
flush();
}
fclose($handle);
}
exit;
}该方案通过 flush() 强制刷新输出缓冲,确保客户端能实时接收数据。相比 readfile(),它更适合处理超过几十 MB 的音频文件,避免了因内存峰值过高导致的 Fatal error。注意 fopen 模式必须为 'rb' 以二进制安全读取。
当 Content-Length 与实际文件大小不一致时会发生什么
如果手动设置的 Content-Length 头部值小于实际文件大小,浏览器在下载完成前可能会认为传输结束,导致文件截断或损坏。反之,如果头部值大于实际大小,浏览器通常会等待更多数据直到超时或连接关闭。在使用 readfile() 时,建议始终通过 filesize() 动态获取真实大小以确保一致性。若无法确定文件大小,可省略 Content-Length 头部,但这样浏览器可能无法显示下载进度条,且某些严格遵循 HTTP 规范的客户端可能会报错。



















