在 PHP 中处理多个并发的 HTTP 请求时,curl_multi_add_handle 是 cURL Multi 接口中的关键函数之一。它允许我们将单个 cURL 句柄加入到一个多句柄会话中,从而实现非阻塞的并发处理。这种机制对于需要处理大文件的分块上传与下载场景非常有用。
分块上传的基本思路是将一个大文件拆分成若干小块,然后并发上传这些小块。使用 curl_multi_add_handle 可以显著提高上传效率。
将大文件拆成多个小文件块(可以使用 fread() 手动分块)。
为每个块创建一个独立的 cURL 请求(使用 PUT 或 POST 方法)。
将这些请求句柄添加到 curl_multi_init() 创建的多句柄管理器中。
使用 curl_multi_exec() 驱动所有上传请求并发执行。
<span><span><span class="hljs-variable">$multiHandle</span></span><span> = </span><span><span class="hljs-title function_ invoke__">curl_multi_init</span></span><span>();
</span><span><span class="hljs-variable">$chunkFiles</span></span><span> = [</span><span><span class="hljs-string">'chunk1.bin'</span></span><span>, </span><span><span class="hljs-string">'chunk2.bin'</span></span><span>, </span><span><span class="hljs-string">'chunk3.bin'</span></span><span>]; </span><span><span class="hljs-comment">// 示例块文件</span></span><span>
</span><span><span class="hljs-variable">$curlHandles</span></span><span> = [];
</span><span><span class="hljs-keyword">foreach</span></span><span> (</span><span><span class="hljs-variable">$chunkFiles</span></span><span> </span><span><span class="hljs-keyword">as</span></span><span> </span><span><span class="hljs-variable">$index</span></span><span> => </span><span><span class="hljs-variable">$file</span></span><span>) {
</span><span><span class="hljs-variable">$ch</span></span><span> = </span><span><span class="hljs-title function_ invoke__">curl_init</span></span><span>(</span><span><span class="hljs-string">'https://example.com/upload_chunk'</span></span><span>);
</span><span><span class="hljs-variable">$fileData</span></span><span> = </span><span><span class="hljs-title function_ invoke__">file_get_contents</span></span><span>(</span><span><span class="hljs-variable">$file</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">curl_setopt</span></span><span>(</span><span><span class="hljs-variable">$ch</span></span><span>, CURLOPT_RETURNTRANSFER, </span><span><span class="hljs-literal">true</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">curl_setopt</span></span><span>(</span><span><span class="hljs-variable">$ch</span></span><span>, CURLOPT_POSTFIELDS, </span><span><span class="hljs-variable">$fileData</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">curl_setopt</span></span><span>(</span><span><span class="hljs-variable">$ch</span></span><span>, CURLOPT_HTTPHEADER, [
</span><span><span class="hljs-string">'Content-Type: application/octet-stream'</span></span><span>,
</span><span><span class="hljs-string">'Content-Length: '</span></span><span> . </span><span><span class="hljs-title function_ invoke__">strlen</span></span><span>(</span><span><span class="hljs-variable">$fileData</span></span><span>),
</span><span><span class="hljs-string">'X-Chunk-Index: '</span></span><span> . </span><span><span class="hljs-variable">$index</span></span><span>
]);
</span><span><span class="hljs-title function_ invoke__">curl_multi_add_handle</span></span><span>(</span><span><span class="hljs-variable">$multiHandle</span></span><span>, </span><span><span class="hljs-variable">$ch</span></span><span>);
</span><span><span class="hljs-variable">$curlHandles</span></span><span>[] = </span><span><span class="hljs-variable">$ch</span></span><span>;
}
</span><span><span class="hljs-comment">// 执行所有上传任务</span></span><span>
</span><span><span class="hljs-variable">$running</span></span><span> = </span><span><span class="hljs-literal">null</span></span><span>;
</span><span><span class="hljs-keyword">do</span></span><span> {
</span><span><span class="hljs-title function_ invoke__">curl_multi_exec</span></span><span>(</span><span><span class="hljs-variable">$multiHandle</span></span><span>, </span><span><span class="hljs-variable">$running</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">curl_multi_select</span></span><span>(</span><span><span class="hljs-variable">$multiHandle</span></span><span>);
} </span><span><span class="hljs-keyword">while</span></span><span> (</span><span><span class="hljs-variable">$running</span></span><span> > </span><span><span class="hljs-number">0</span></span><span>);
</span><span><span class="hljs-comment">// 清理资源</span></span><span>
</span><span><span class="hljs-keyword">foreach</span></span><span> (</span><span><span class="hljs-variable">$curlHandles</span></span><span> </span><span><span class="hljs-keyword">as</span></span><span> </span><span><span class="hljs-variable">$ch</span></span><span>) {
</span><span><span class="hljs-title function_ invoke__">curl_multi_remove_handle</span></span><span>(</span><span><span class="hljs-variable">$multiHandle</span></span><span>, </span><span><span class="hljs-variable">$ch</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">curl_close</span></span><span>(</span><span><span class="hljs-variable">$ch</span></span><span>);
}
</span><span><span class="hljs-title function_ invoke__">curl_multi_close</span></span><span>(</span><span><span class="hljs-variable">$multiHandle</span></span><span>);
</span></span>
对于分块下载,同样的机制也适用。通过设置 Range 头部,我们可以请求文件的特定字节区间,从而并发下载整个文件的不同部分,之后再合并这些部分。
获取目标文件的总大小。
将其划分为多个区段,例如每段 1MB。
为每段构建一个带 Range 头的 cURL 请求。
利用 curl_multi_add_handle 加入多句柄会话进行并发下载。
按顺序合并下载结果。
<span><span><span class="hljs-variable">$url</span></span><span> = </span><span><span class="hljs-string">'https://example.com/largefile.zip'</span></span><span>;
</span><span><span class="hljs-variable">$fileSize</span></span><span> = </span><span><span class="hljs-number">10000000</span></span><span>; </span><span><span class="hljs-comment">// 假设文件大小为10MB</span></span><span>
</span><span><span class="hljs-variable">$chunkSize</span></span><span> = </span><span><span class="hljs-number">1000000</span></span><span>; </span><span><span class="hljs-comment">// 每块1MB</span></span><span>
</span><span><span class="hljs-variable">$multiHandle</span></span><span> = </span><span><span class="hljs-title function_ invoke__">curl_multi_init</span></span><span>();
</span><span><span class="hljs-variable">$curlHandles</span></span><span> = [];
</span><span><span class="hljs-variable">$results</span></span><span> = [];
</span><span><span class="hljs-keyword">for</span></span><span> (</span><span><span class="hljs-variable">$start</span></span><span> = </span><span><span class="hljs-number">0</span></span><span>; </span><span><span class="hljs-variable">$start</span></span><span> < </span><span><span class="hljs-variable">$fileSize</span></span><span>; </span><span><span class="hljs-variable">$start</span></span><span> += </span><span><span class="hljs-variable">$chunkSize</span></span><span>) {
</span><span><span class="hljs-variable">$end</span></span><span> = </span><span><span class="hljs-title function_ invoke__">min</span></span><span>(</span><span><span class="hljs-variable">$start</span></span><span> + </span><span><span class="hljs-variable">$chunkSize</span></span><span> - </span><span><span class="hljs-number">1</span></span><span>, </span><span><span class="hljs-variable">$fileSize</span></span><span> - </span><span><span class="hljs-number">1</span></span><span>);
</span><span><span class="hljs-variable">$ch</span></span><span> = </span><span><span class="hljs-title function_ invoke__">curl_init</span></span><span>(</span><span><span class="hljs-variable">$url</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">curl_setopt</span></span><span>(</span><span><span class="hljs-variable">$ch</span></span><span>, CURLOPT_RETURNTRANSFER, </span><span><span class="hljs-literal">true</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">curl_setopt</span></span><span>(</span><span><span class="hljs-variable">$ch</span></span><span>, CURLOPT_RANGE, </span><span><span class="hljs-string">"<span class="hljs-subst">$start</span></span></span><span>-</span><span><span class="hljs-subst">$end</span></span><span>");
</span><span><span class="hljs-title function_ invoke__">curl_multi_add_handle</span></span><span>(</span><span><span class="hljs-variable">$multiHandle</span></span><span>, </span><span><span class="hljs-variable">$ch</span></span><span>);
</span><span><span class="hljs-variable">$curlHandles</span></span><span>[] = </span><span><span class="hljs-variable">$ch</span></span><span>;
}
</span><span><span class="hljs-comment">// 执行所有下载任务</span></span><span>
</span><span><span class="hljs-variable">$running</span></span><span> = </span><span><span class="hljs-literal">null</span></span><span>;
</span><span><span class="hljs-keyword">do</span></span><span> {
</span><span><span class="hljs-title function_ invoke__">curl_multi_exec</span></span><span>(</span><span><span class="hljs-variable">$multiHandle</span></span><span>, </span><span><span class="hljs-variable">$running</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">curl_multi_select</span></span><span>(</span><span><span class="hljs-variable">$multiHandle</span></span><span>);
} </span><span><span class="hljs-keyword">while</span></span><span> (</span><span><span class="hljs-variable">$running</span></span><span> > </span><span><span class="hljs-number">0</span></span><span>);
</span><span><span class="hljs-comment">// 收集结果并合并</span></span><span>
</span><span><span class="hljs-keyword">foreach</span></span><span> (</span><span><span class="hljs-variable">$curlHandles</span></span><span> </span><span><span class="hljs-keyword">as</span></span><span> </span><span><span class="hljs-variable">$ch</span></span><span>) {
</span><span><span class="hljs-variable">$results</span></span><span>[] = </span><span><span class="hljs-title function_ invoke__">curl_multi_getcontent</span></span><span>(</span><span><span class="hljs-variable">$ch</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">curl_multi_remove_handle</span></span><span>(</span><span><span class="hljs-variable">$multiHandle</span></span><span>, </span><span><span class="hljs-variable">$ch</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">curl_close</span></span><span>(</span><span><span class="hljs-variable">$ch</span></span><span>);
}
</span><span><span class="hljs-title function_ invoke__">curl_multi_close</span></span><span>(</span><span><span class="hljs-variable">$multiHandle</span></span><span>);
</span><span><span class="hljs-comment">// 写入完整文件</span></span><span>
</span><span><span class="hljs-title function_ invoke__">file_put_contents</span></span><span>(</span><span><span class="hljs-string">'merged_file.zip'</span></span><span>, </span><span><span class="hljs-title function_ invoke__">implode</span></span><span>(</span><span><span class="hljs-string">''</span></span><span>, </span><span><span class="hljs-variable">$results</span></span><span>));
</span></span>
连接数限制:并发过多可能导致服务器拒绝连接,建议控制并发数量(如每次处理 5 个)。
错误处理:使用 curl_error() 和 curl_multi_info_read() 检查每个请求的状态。
超时设置:建议设置 CURLOPT_TIMEOUT 防止个别请求卡住整个流程。
curl_multi_add_handle 是构建并发 HTTP 请求核心工具之一。通过它结合分块策略,可以高效处理大文件的上传和下载任务。合理组织请求、并发控制、错误处理是实际应用中必须考虑的重要因素。使用得当,它能够极大地提升 PHP 网络通信的性能和可靠性。