webman 适合做股票行情接口,因其在 swoole 高性能基础上封装了连接管理、协程上下文、中间件等能力,避免手动处理心跳、资源泄漏、协程污染等问题;原生 swoole\http\server 易因 onclose 未清理或协程误用导致内存上涨或阻塞。

为什么 Webman 适合做股票行情接口,而不是 Swoole\Http\Server 直接写?
因为 Webman 在保持 Swoole 底层高性能的同时,封装了连接管理、协程上下文、中间件、路由复用等能力,省去手动处理心跳、连接泄漏、协程变量污染等高频出错点。直接用原生 Swoole\Http\Server 写行情接口,500 行内容易跑通,但上线后常因 onClose 没清资源导致内存缓慢上涨,或忘记 go 启动协程导致阻塞主线程。
实操建议:
- 用
Webman的ConnectionInterface接管 WebSocket 连接,比自己 new Swoole\WebSocket\Server 更稳 - 行情推送必须走协程安全的
connection->send(),不能在非协程环境调用(比如定时器里没go包裹) - 避免在
onMessage里做耗时解析(如 JSON 解析大包),改用json_decode($data, flags: JSON_THROW_ON_ERROR)防止静默失败
onOpen 里该不该立刻发全量行情?
不该。新连接上来就推全量数据,会瞬间打满带宽和客户端渲染能力,尤其当单机承载 1w+ 连接时,首屏卡顿、丢帧严重。真实场景中,客户端通常只订阅几个代码,不是全市场。
实操建议:
- 在
onOpen只返回握手成功响应,例如{"code":0,"msg":"connected"} - 等收到客户端第一个
{"type":"subscribe","symbols":["SH600519","SZ000858"]}消息后再加载对应行情快照 + 启动增量推送 - 用
array_key_exists($symbol, $this->subscribed)而非in_array()判断是否已订阅,避免 O(n) 查找拖慢高频推送路径
如何安全地从外部(如 Redis Pub/Sub 或 Kafka)把行情推给 WebSocket 连接?
核心难点是:Swoole 的 WebSocket 连接对象只在 worker 进程内有效,而消费 Kafka/Redis 的协程可能不在同一个 worker。直接跨进程调用 $connection->send() 会报 Connection is closed 或静默失败。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
实操建议:
- 用
Webman\Process启一个独立进程消费行情源,通过InterProcess::send()把 symbol 和数据发到主 worker 进程 - 主 worker 在
onWorkerStart注册InterProcess::onReceive(),收到后用foreach ($connections as $conn)找出已订阅该 symbol 的连接再 send - 不要在接收回调里做复杂计算(如格式转换),先存到
chan或array,由定时器或事件循环异步分发,防止阻塞事件循环
为什么压测时 CPU 上不去,但连接数一过 3k 就开始丢消息?
大概率是 send() 调用触发了底层 TCP 缓冲区满,而默认没有开启 websocket->push() 的 buffer 策略或错误重试。Swoole 的 send() 是异步非阻塞的,但若对端网络慢或未及时 recv,缓冲区积压后新消息会被丢弃,且不报错。
实操建议:
- 启用
websocket->set(['websocket_buffer_size' => 2 * 1024 * 1024])提高单连接缓冲上限(注意内存占用) - 在
send()后检查返回值:if ($connection->send($data) === false) { $connection->close(); } - 对关键行情(如涨停价变动),加简单重试逻辑(最多 2 次,间隔 10ms),用
co::sleep(0.01)避免忙等
真正难的不是推得快,而是推得稳——每个连接的生命周期、订阅关系、发送队列、断线重连状态,都得在内存里精确维护。这些细节不写进代码注释,过两个月自己都得重读一遍逻辑。










