Workerman中需在onConnect回调里调用socket_set_option($connection->getSocket(), SOL_TCP, TCP_NODELAY, 1)启用TCP_NODELAY;仅设此选项不够,还需同步调大net.core.rmem_max、开启tcp_window_scaling=1,并避免sendfile与output_buffering干扰。

Workerman 默认不开启 TCP_NODELAY,小包延迟高时必须手动启用,否则高频 JSON 或心跳帧会被内核卡住 20–200ms 才发出。
Workerman 中怎么设 TCP_NODELAY?
Workerman 没有直接叫 tcp_nodelay 的配置项,它依赖底层 PHP socket 的 TCP_NODELAY 选项,需在 onConnect 回调里对连接句柄显式设置:
- 用
$connection->getSocket()获取原始 socket 资源 - 调用
stream_set_option($socket, STREAM_CONTEXT_OPTS, ...)不起作用,必须用socket_set_option() - 正确写法是:
socket_set_option($connection->getSocket(), SOL_TCP, TCP_NODELAY, 1) - 这个操作必须在每次新连接建立后立即执行,即放在
onConnect里,不能只设一次全局
为什么只开 TCP_NODELAY 还不够?
开了 TCP_NODELAY 只解决“发不出去”的问题,但 Workerman 场景下常连带暴露其他瓶颈:
生成Blender Python (bpy)脚本,该脚本使用node_groups API和GeometryNodeTree接口程序化地创建几何节点修改器树,用于参数化3D资产生成。
- 如果
net.core.rmem_max小于tcp_rmem第三项(比如设成 262144),内核自动扩窗失效,接收缓冲区被硬截断,导致丢包重传——ss -i查到的rcv_space长期卡在 256KB 就是这个信号 - 没配
tcp_window_scaling=1,窗口上限锁死在 64KB,长 RTT 下吞吐直接腰斩 -
worker::$maxConnections设太小,连接排队在 accept 队列,net.core.somaxconn不同步调大,新连接会超时失败 - PHP 层用了
json_encode()发送大量小响应,序列化耗时可能比网络延迟还高——换msgpack或二进制协议更实在
哪些路径/连接必须开,哪些可以不开?
不是所有连接都适合开 TCP_NODELAY,关键看数据特征:
- 必须开:WebSocket 实时消息、SSE 流、API 网关返回的轻量 JSON(如
/health、/user/status)、心跳帧({"type":"ping"})——这些响应 ≤1KB、高频、长连接 - 建议关或跳过:文件上传接收端、大块 HTML 内联 JS(>5KB)、日志上报批量 POST——这类走吞吐优先,Nagle 算法反而能合包省系统调用
- 完全无效:短连接接口(如传统 REST API 每次请求建新连),因为
TCP_NODELAY是 per-connection 选项,连接一断就重置
容易被忽略的系统层冲突点
Workerman 进程跑在 Linux 上,内核参数和 PHP 行为之间存在隐性耦合:
-
sendfile()和TCP_NODELAY互斥:Workerman 若启用了零拷贝发送(如某些自定义协议中调stream_copy_to_stream),TCP_NODELAY会被绕过,不起作用 - Nginx 反向代理时,
tcp_nodelay on必须配在location块里且确保keepalive_timeout已设,否则 Nginx 自己先关了长连接,Workerman 的TCP_NODELAY就成了摆设 - PHP 的
output_buffering如果开启(尤其 CLI 模式下误继承 php.ini 设置),会把$connection->send()的内容先缓在 PHP 用户态,等 buffer 满或脚本结束才刷出——这比 Nagle 延迟还不可控
真正生效的链路是:PHP socket 选项 → 内核 TCP 栈 → 网络设备驱动。任何一个环节被默认值、中间件或错误配置截断,TCP_NODELAY 就只是个开关而已。

















