move_uploaded_file对大文件必然失败,因PHP强制先将整个文件写入临时目录再搬运,受upload_max_filesize等限制,且会耗尽memory_limit;可行方案是弃用$_FILES,改用php://input流式接收分片并流式合并。

move_uploaded_file 为什么对大文件必然失败
因为 PHP 默认上传流程强制走 $_FILES,整个文件必须先完整写入临时目录(upload_tmp_dir),再由脚本调用 move_uploaded_file 搬运。这不是“慢”,而是架构级硬伤:
-
upload_max_filesize和post_max_size会在请求层直接拦截超限请求(返回 413 或空白页) - 即使绕过大小限制,
$_FILES['file']['tmp_name']被加载后,move_uploaded_file内部仍会尝试映射或复制整个文件句柄——对 2GB 文件,worker 进程 RSS 瞬间飙高,memory_limit很快耗尽 - 若后续还要
file_get_contents合并分片,等于二次内存加载,必爆
真正可行的入口只有 php://input:它不经过 $_FILES,前端必须弃用 multipart/form-data 表单提交,改用 fetch 发送 application/octet-stream 或自定义二进制载荷。
Webman 接收分片必须跳过 $_FILES + 手动解析流
Webman 基于 Workerman,是常驻进程模型,不能依赖 PHP-FPM 那套上传配置。你必须在路由 handler 中直接读原始输入流,并自行管理分片存储路径与命名规则:
- 禁用所有
$_FILES相关逻辑,不要调用$request->file()或任何封装了$_FILES的方法 - 用
$request->getBody()->getContents()获取原始二进制流(注意:仅适用于单分片小请求;大分片需用stream_copy_to_stream流式落盘) - 分片命名建议格式:
{uploadId}_{chunkIndex}.{ext},例如abc123_005.bin,避免用时间戳或随机字符串——合并时需严格保序 - 临时目录必须可写且独立于
upload_tmp_dir,推荐设为runtime/uploads/chunks/,并在上传前确保父目录存在
合并分片时别用 file_get_contents + file_put_contents
这是 Webman 项目中最隐蔽的内存雷区。哪怕单个分片只有 5MB,若用 file_get_contents 全量读入再 file_put_contents 写出,PHP 会为每一分片额外分配一块连续内存,多并发合并时极易触发 OOM。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
正确做法是流式拼接:
- 用
fopen($finalPath, 'wb')打开目标文件,保持句柄 - 遍历已上传分片路径,对每个分片执行:
$fp = fopen($chunkPath, 'rb'); stream_copy_to_stream($fp, $finalHandle); fclose($fp); - 全程无全量加载,内存占用恒定在几 KB 级别
- 合并完成后立即
unlink所有分片,防止磁盘被占满
Webman 进程内存持续增长必须主动干预
Webman 是长驻进程,不像传统 PHP 请求结束后自动释放内存。静态变量、未清理的缓存、未关闭的资源句柄都会累积——尤其在高频上传场景下,memory_get_peak_usage() 可能每小时上涨几十 MB。
- 在合并完成后的 handler 尾部加
gc_collect_cycles(),强制触发 GC(不是万能,但对释放循环引用有效) - 检查所有中间件和监听器中是否用了
static $cache = [],若有,必须加容量上限和自动截断逻辑,例如:if (count($cache) > 500) array_shift($cache); - 不要依赖
memory_limit = -1,这只会掩盖泄漏,让问题在凌晨三点爆发 - 生产环境务必启用软重启策略:在
config/process.php中配置'max_request' => 1000,让 worker 处理千次请求后优雅退出并被 supervisor 拉起新进程
最易被忽略的是:分片接收阶段没做哈希校验,导致损坏分片静默写入;合并时没校验总文件哈希,结果传了个半截文件还当成成功返回——这两步必须加,否则优化再好也白搭。


















