co::set() 必须在 co::run() 内且所有 go() 前调用,仅协程内生效;swoole_hook_tcp 管流函数,swoole_hook_curl 专管 curl_exec();file_get_contents 协程化取决于协议头,http 需 tcp,本地文件需 file。

Co::set() 的 hook_flags 必须在协程内设置,否则直接报错
很多人一上来就写 Co::set(['hook_flags' => SWOOLE_HOOK_TCP]),结果抛出 Invalid argument: Co::set() must be called in coroutine。这不是配置错了,是调用时机错了——Co::set() 只能在协程上下文中生效,主进程(即 Co\run() 外)调用就是非法操作。
- ✅ 正确姿势:必须在
Co\run()内部、且在任何go()启动前调用 - ❌ 错误姿势:
Co::set()放在文件顶部、或go()回调里单独调用(会导致部分协程没被 Hook) - ⚠️ 注意:
Co::set()是当前协程局部生效的,但它的作用是“为后续所有子协程设默认钩子”,所以只需调一次,且必须最早
SWOOLE_HOOK_TCP 和 SWOOLE_HOOK_CURL 不是一回事,别混着用
SWOOLE_HOOK_TCP 管的是 PHP 原生流函数,比如 fsockopen()、stream_socket_client()、file_get_contents('http://');而 SWOOLE_HOOK_CURL 专治 curl_exec() ——哪怕你启用了 SWOOLE_HOOK_ALL,它也不会自动包含 cURL,因为底层实现完全不同(libcurl 不走 PHP 流层)。
- 用 Guzzle?必须开
SWOOLE_HOOK_TCP(它内部用stream_socket_client) - 用原生
curl_init()+curl_exec()?不加SWOOLE_HOOK_CURL就会阻塞整个 Worker - 推荐生产配置:
SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL,显式补上 cURL
file_get_contents('http://') 能协程化,但 file_get_contents('/tmp/a.txt') 不行
这是最常被误解的一点:file_get_contents() 是否协程化,取决于协议头,不是函数名。HTTP/HTTPS URL 触发的是流层网络请求,归 SWOOLE_HOOK_TCP 管;本地文件路径走的是系统 I/O,需要 SWOOLE_HOOK_FILE 单独开启。
- ✅
file_get_contents('http://api.com')→ 需SWOOLE_HOOK_TCP - ✅
file_get_contents('/var/log/app.log')→ 需SWOOLE_HOOK_FILE - ⚠️ 混合使用时,两个 flag 都得开:
SWOOLE_HOOK_TCP | SWOOLE_HOOK_FILE - ? 验证是否生效:启动两个
go()并发调用sleep(1),如果总耗时接近 1 秒,说明 Hook 成功;如果接近 2 秒,大概率没生效或位置错了
Hook 不是万能的,有些扩展和函数它真钩不住
Swoole 的 Hook 本质是函数指针劫持,只覆盖了它明确注册过的函数列表。像 pdo_mysql 的 PDO::query()、mysqli::query() 这类数据库方法,虽然底层也走 socket,但它们绕过了 PHP 流层,Swoole 默认不 Hook——得靠对应客户端库自己适配协程(比如 Swoole 自研的 swoole_mysql 或 Hyperf 的协程 PDO 封装)。
- 常见“钩不住”的情况:
proc_open()(需SWOOLE_HOOK_PROC)、gethostbyname()(需SWOOLE_HOOK_SLEEP或SWOOLE_HOOK_ALL)、Redis 扩展(原生 phpredis 不协程,要用co\Redis) - 判断依据:查官方文档的
ext-src/swoole_runtime.cc中hook_func()注册表,或运行时看strace是否出现epoll_wait - 最稳妥的做法:不用猜,直接在关键调用前后加
\Swoole\Coroutine::getCid()打日志,确认是否真在协程中执行
真正难的从来不是记全所有 flag,而是搞清每个 I/O 调用背后走的是哪一层——流?cURL?系统调用?还是扩展私有 socket?漏掉一层,就卡死一个协程。










