必须使用 Swoole\Coroutine\Http\Client 实现协程 HTTP 请求,禁用 curl_init() 和 file_get_contents();并发需配合 Channel 收集结果;须显式检查 get()/post() 返回值及 errCode/errMsg 处理超时与错误;注意协程上下文隔离与日志标识。

协程 HTTP 客户端必须用 Swoole\Coroutine\Http\Client
直接 new curl_init() 或用 file_get_contents() 在协程里会阻塞整个进程,等同于没开协程。Swoole 的协程调度只对自家封装的 I/O 操作透明,比如 Swoole\Coroutine\Http\Client、Swoole\Coroutine\MySQL、Swoole\Coroutine\Redis。其他扩展(包括 Guzzle 默认配置)走的是 PHP 原生阻塞网络调用,协程无法挂起它们。
常见错误现象:go(function () { file_get_contents('https://api.example.com'); }); 看似并发,实际是串行执行,且 CPU 占用高、响应慢。
- 必须实例化
Swoole\Coroutine\Http\Client,设置set(['timeout' => 5])防止单个请求拖垮整体 - GET 请求用
$client->get('/path'),POST 用$client->post('/path', $data),返回布尔值,成功后读$client->body - 注意:不支持 HTTPS 时未启用 OpenSSL 扩展会导致连接失败,错误信息类似
SSL operation failed
并发发起多个请求要用 go() + chan 收集结果
单纯写一堆 go(function () { ... }); 不够——你拿不到返回值,也没法控制并发数或等待全部完成。正确做法是配合 Swoole\Coroutine\Channel,每个协程把结果 push 进去,主协程 pop 等待收齐。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
示例场景:同时查 3 个用户信息接口,汇总返回:
// 创建容量为 3 的 channel
$chan = new Swoole\Coroutine\Channel(3);
for ($i = 0; $i < 3; $i++) {
go(function () use ($chan, $i) {
$cli = new Swoole\Coroutine\Http\Client('api.example.com', 443, true);
$cli->set(['timeout' => 3]);
if ($cli->get("/user/{$i}")) {
$chan->push(['id' => $i, 'data' => json_decode($cli->body, true)]);
} else {
$chan->push(['id' => $i, 'error' => $cli->errMsg]);
}
});
}
// 主协程收集全部结果(顺序不定)
$results = [];
for ($i = 0; $i < 3; $i++) {
$results[] = $chan->pop();
}
- 不设
Channel容量或设太小(如 1),可能导致协程阻塞在push上,影响并发吞吐 - 别用全局数组或引用传参收集结果——协程间变量不共享,写进去也读不到
- 如果接口有依赖关系(比如第二个请求要第一个的结果),就别硬塞进并发,改用
co::sleep()或串行调用
超时和错误处理不能只靠 try-catch
Swoole\Coroutine\Http\Client 的 get()/post() 方法本身不抛异常,失败时返回 false,错误细节存在 $client->errMsg 和 $client->errCode 里。PHP 的 try-catch 捕获不到 DNS 超时、连接拒绝这类底层错误。
- 必须显式检查
if (!$client->get(...)),而不是假设它一定成功 -
$client->errCode === SOCKET_ETIMEDOUT表示超时,=== 111是 Connection refused,这些数字得查man 2 errno或 Swoole 文档 - 建议统一包装一层:返回
['ok' => true, 'data' => ...]或['ok' => false, 'code' => $client->errCode, 'msg' => $client->errMsg] - 注意:DNS 解析失败(
errCode === -5)常被忽略,但内网环境若没配好/etc/resolv.conf或启用enable_coroutine_dns_cache,就会卡住
协程上下文丢失是调试中最容易被忽略的点
你在 go() 里打印 getmypid() 会发现全是同一个进程 ID,但 debug_backtrace()、error_log() 的行号可能错乱,尤其用了中间件或框架封装后。更麻烦的是:协程间不共享 static 变量、global、$GLOBALS,甚至 $_SERVER 在某些版本里也不可靠。
- 日志写入必须带协程 ID:
swoole_coroutine_getid(),否则多请求混在一起根本分不清谁是谁 - 不要在协程里改
ini_set('default_socket_timeout', ...),它不生效;要用$client->set(['timeout' => ...]) - 框架集成(如 Hyperf、Swoft)会自动处理上下文传递,但裸用 Swoole 时,用户 ID、请求 ID、TraceID 这类关键字段得手动通过参数传进
go()闭包

















