sendfile适用于本地磁盘静态文件的零拷贝下载,要求路径真实、禁用gzip且支持随机读;createdownloadstream适用于需中间处理、网络存储或动态生成的流式下载,支持协程限速与中断。

sendfile 与 createDownloadStream 的适用场景差异
两者都用于大文件下载,但底层机制和触发条件完全不同。sendfile 是内核级零拷贝系统调用,直接由操作系统将文件数据从磁盘文件描述符送入 socket 缓冲区,不经过 PHP 用户态内存;而 createDownloadStream(Swoole ≥4.8)是协程级流式封装,它在用户态分块读取文件、写入响应体,适合需要中间处理(如加解密、限速、日志埋点)的场景。
常见错误现象:对 GB 级静态资源仍用 readfile 或 file_get_contents + echo,导致内存暴涨、Worker 被卡死甚至 OOM;或误以为 createDownloadStream 也走零拷贝,结果发现 CPU 占用偏高。
-
sendfile要求文件路径真实存在、可读,且 Web 服务器未启用 gzip 压缩(否则会禁用) -
createDownloadStream支持协程中断、可配合Swoole\Coroutine::sleep实现平滑限速,但需自行设置Content-Length和Content-Disposition - 若文件位于 NFS 或 Ceph 等网络存储,
sendfile可能失效(部分内核不支持跨文件系统 sendfile),此时必须退到createDownloadStream
断点续传不是“自动开启”,而是依赖 Range 头解析逻辑
sendfile 方法在收到客户端带 Range 头的请求时,会自动返回 206 Partial Content、设置 Content-Range 和 Accept-Ranges: bytes,但前提是:HTTP 请求已正确解析、且文件支持随机读取(普通磁盘文件满足,但某些只读挂载或加密卷可能不支持)。
容易踩的坑:Nginx 前置代理时默认会剥离 Range 头,或配置了 proxy_buffering on 导致整个响应被缓存后才发给客户端,断点续传彻底失效;又或者开发者手动设置了 $response->status(200),覆盖了 sendfile 内部的 206 判定逻辑。
- 务必检查
$request->header['range']是否非空,再决定是否调用sendfile(避免无意义 fallback) - 不要在调用
sendfile前调用$response->header()设置Status,它会干扰状态码自动推导 - 若需自定义
Content-Disposition文件名,应在sendfile调用前设置,否则头可能被忽略
动态生成/加密文件必须放弃 sendfile,改用协程分块 write
当文件内容需实时生成(如 ZIP 打包)、或存储为加密 blob(AES-GCM 加密后存入数据库),sendfile 完全不可用——它只接受本地磁盘路径。此时唯一可行路径是:打开资源句柄 → 协程中循环 fread / openssl_decrypt / ZipArchive::getFromIndex → $response->write() 分块输出。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
性能关键点不在“能不能做”,而在“每块多大”和“是否阻塞”。设得太小(如 1KB)会导致 syscall 过多、网络包碎片化;设得太大(如 1MB)又可能压爆协程栈或触发 TCP Nagle 算法延迟。
- 推荐缓冲块大小为
64 * 1024(64KB),兼顾吞吐与延迟 - 每次
$response->write()后建议加if (co::getuid()) co::sleep(0.001),让出调度权,防止单个下载长期独占协程 - 务必在
try/catch中包裹fread,并检查返回值:false表示 I/O 错误,''表示 EOF,不能只判空字符串
并发压测下 sendfile 的实际瓶颈常在磁盘 I/O 或 inode 锁
很多人以为 sendfile 是“万能加速器”,但在 1000+ 并发下载同一文件时,反而比 createDownloadStream 更慢。原因在于:Linux 内核对同一文件的 sendfile 调用会竞争 inode 锁,尤其在 ext4 上表现明显;而协程流式读取使用独立文件描述符,锁粒度更细。
这不是代码问题,是底层设计使然。所以高并发同文件下载(如软件更新包分发),反而应关闭 sendfile,启用 createDownloadStream + opcache.enable_file_override=1(若文件为 PHP 脚本生成)来提升整体吞吐。
- 可通过
iostat -x 1观察%util和await,确认是否磁盘成为瓶颈 - ext4 默认启用
dir_index和filetype,但大文件密集读场景建议加挂noatime,nodiratime - 若用 XFS,可考虑
xfs_info检查是否启用allocsize=64k,匹配 sendfile 块大小
真正难处理的不是“怎么选 API”,而是当文件路径来自用户输入、存储介质混用(本地 SSD + 对象存储 + 加密卷)、且需统一支持断点续传和限速时,sendfile 和 createDownloadStream 都无法直接复用,必须抽象一层流适配器——这部分逻辑一旦漏掉权限校验或路径遍历防护,就直接变成任意文件读取漏洞。










