workerman处理大文件上传不能依赖$_files和php://input,必须通过$request->rawbody()或$connection->recv()获取原始二进制流,手动流式解析multipart边界、逐行定位payload、精确截取写入,并用file_id/chunk_index隔离并发、redis幂等校验、cat命令合并防内存溢出。

Workerman 处理大文件上传不能依赖 $_FILES,必须手动解析原始请求体;流式传输的核心是不把整段数据读进内存,而是边收边截、边写边校验——否则 50MB 文件就可能让 Worker 进程卡死或 OOM。
为什么 $_FILES 和 php://input 都失效?
Workerman 是常驻进程的异步服务器,不走 PHP-FPM 生命周期,所以 $_FILES 永远为空;php://input 在 Workerman 下不可用,它只对 CGI/FPM 环境有效,且不支持分块传输(chunked encoding)。
所有上传数据都裸露在:
-
$request->rawBody()(HTTP 协议栈下) - 或
$connection->recv()(TCP 自定义协议下)
你拿到的是一整段带 boundary 的二进制流,不是结构化数组。
怎么安全提取 multipart 分片?三步流式定位
关键不是“解析表单”,而是“按 HTTP 协议逐行扫描边界,精确截取 payload”。不能等 body 收全再处理,必须流式推进。
- 从
Content-Typeheader 提取boundary=...,构造匹配字符串(如--1234567890) - 用
fgets()或stream_get_line()逐行读原始 body 流,跳过 header 行,直到遇到--{boundary}和Content-Disposition: form-data; name="file"; filename="xxx" - 真正有效的文件数据从下一个
\r\n\r\n开始,到下一个--{boundary}前结束;用fread($stream, $length)精确读取,长度需根据前后 boundary 位置动态计算
示例片段(非完整代码,仅示意逻辑):
while (!feof($body_stream)) {
$line = fgets($body_stream);
if (preg_match('/^--' . preg_quote($boundary) . '--$/', $line)) break;
if (strpos($line, 'filename="') !== false) {
// 找到字段头,下一行必为 \r\n\r\n,之后就是 payload
fgets($body_stream); // 跳过空行
$chunk = fread($body_stream, $expected_size);
file_put_contents($tmp_path, $chunk, FILE_APPEND | LOCK_EX);
}
}
并发写入和断点续传怎么防冲突?
多个客户端同时上传同名文件,或者同一文件分片重试,极易覆盖或乱序。必须靠元数据隔离 + 幂等控制。
- 前端必须传三个关键字段:
file_id(全局唯一,如文件 MD5)、chunk_index(从 0 开始)、total_chunks - 每个分片独立写入临时路径:
/tmp/upload_{file_id}_{chunk_index}.part,不要用FILE_APPEND拼接——它不是原子操作 - 合并前检查所有
.part是否齐全,并逐个校验md5(前端随分片提交,服务端流式重算比对) - Redis 中存
{file_id}:{chunk_index}标记已接收,重复请求直接返回 200
合并命令建议用 cat /tmp/upload_abc_0.part /tmp/upload_abc_1.part > /final/file.zip,而不是 PHP 的 file_get_contents 全量加载。
最容易被忽略的是临时文件生命周期管理:Workerman 不会自动清理 upload_tmp_dir,你得自己加定时任务或 onWorkerStop 回调删过期 .part;还有中文文件名必须从 ISO-8859-1 转回 UTF-8,否则 move_uploaded_file 类逻辑会失败。











