workerman 不支持零拷贝,因其基于 php 用户态运行,无法透传 sendfile/splice 等系统调用;真正零拷贝需由 nginx 等 web 服务器接管静态文件服务并启用 sendfile on。

Workerman 本身不直接提供零拷贝系统调用(如 sendfile 或 splice)的封装接口,也不能像 Nginx 那样在内核态完成文件到 socket 的直通传输。这是由 PHP 运行时模型和 Workerman 的设计定位决定的:它基于用户态事件循环(libevent/ev),所有 I/O 操作最终仍需经由 PHP 的 fread/fwrite 或 stream_socket_sendto 等函数,数据必然经过用户缓冲区。
所以结论很明确:你无法在 Workerman 中“开启”或“配置”零拷贝——它压根不支持 sendfile 系统调用的透传,也没有 mmap+writev 的底层控制能力。
那为什么有些项目说“Workerman 配合零拷贝”?多半是混淆了概念,或者把「应用层缓冲复用」误称为零拷贝。下面说清楚怎么做、为什么、以及最容易踩的坑。
Workerman 发送静态文件时实际走的是传统四次拷贝路径
当你用类似以下方式发送一个文件:
$connection->send(file_get_contents($filepath));
它等价于:
-
file_get_contents→ 触发read():磁盘 → 内核 buffer → 用户 buffer(2 次拷贝) -
$connection->send()→ 调用write()或send():用户 buffer → 内核 socket buffer → 网卡(再 2 次拷贝)
全程 4 次拷贝 + 至少 2 次上下文切换。PHP 进程还额外承担了内存分配(file_get_contents 分配完整文件内存)、GC 压力、大文件阻塞事件循环等问题。
替代方案:绕过 PHP 用户空间,交还给内核
真正可行的零拷贝路径,是让 Web 服务器(如 Nginx)接管静态文件服务,Workerman 只处理动态逻辑:
- 把静态资源(
/static/,/upload/)全部配置为 Nginx 的location,启用sendfile on - Nginx 配置示例:
location /static/ { alias /var/www/static/; sendfile on; tcp_nopush on; expires 1h; } - 所有对
/static/xxx.jpg的请求,根本不会进Workerman,Nginx 直接调用sendfile(2)完成零拷贝发送
如果你必须用 Workerman 处理静态文件(比如权限校验后下发),唯一能接近零拷贝效果的方式是:
- 使用
fopen+stream_copy_to_stream(但仍是用户态中转) - 或借助
proc_open调用外部命令(如cat $file | nc),不可控且危险 - 没有安全、稳定、可维护的纯 PHP 零拷贝路径
Workerman 内部优化能做的只有“减少冗余拷贝”,不是零拷贝
Workerman 提供的所谓“高性能”来自异步非阻塞,但它对文件 I/O 的优化非常有限:
- 不支持
sendfile:PHP 的stream_socket_sendto和fwrite底层不走sendfile系统调用 - 不支持
splice:ext/sockets和ext/streams均未暴露该能力 -
Worker::$onMessage回调里用file_get_contents读大文件会阻塞整个进程(单线程 event loop)
你可以做的务实改进包括:
- 对小文件(file_get_contents +
$connection->send(),但加ob_flush和分块发送逻辑 - 对大文件,改用
fopen+ 循环fread+$connection->send(),并配合connection->pause()防止压垮连接 - 启用
Worker::$reusePort = true和Worker::$daemonize = true提升并发吞吐,但这和零拷贝无关
容易被忽略的关键点
-
sendfile要求源 fd 是普通文件(不能是 pipe、socket、/proc),且目标 socket 必须是 TCP;Workerman的 socket fd 是 PHP stream 封装,无法传入原始 fd 给sendfile - 即使你用
FFI或ext-curl手动调用sendfile,PHP 的 socket 生命周期、buffer 管理、错误恢复机制也完全不匹配,极易 crash 或丢数据 - 所有号称“Workerman 零拷贝”的第三方扩展,基本都是包装了用户态内存池或 chunked write,不是真正的零拷贝
别在 Workerman 上死磕零拷贝。该交给 Nginx 的就交出去,该用 sendfile 的地方确保它真正在生效——检查 strace -e trace=sendfile,read,write nginx 是否真调用了 sendfile,而不是 fallback 到 read/write。











