Symfony 6.0 无法仅靠内置组件实现 WebSocket,因其基于 HTTP 请求-响应模型,依赖 PHP-FPM/CGI 短生命周期,而 WebSocket 需常驻事件循环进程;symfony/websocket-server 尚未发布(6.4+ 才有实验性支持),且控制器内启动会阻塞主进程、被反向代理超时断连。

Symfony 6.0 本身不提供 WebSocket 服务,直接用 symfony/http-kernel 或 symfony/messenger 无法建立长连接;必须引入外部 WebSocket 服务器(如 Ratchet、Swoole 或第三方服务),Symfony 只负责消息分发与业务逻辑协调。
为什么不能只靠 Symfony 内置组件实现 WebSocket
Symfony 是基于 HTTP 协议的请求-响应式框架,其核心生命周期依赖于 PHP-FPM 或 CGI 模式——每个请求处理完即释放资源,无法维持客户端长连接。WebSocket 是独立于 HTTP 的全双工协议,需要常驻内存的事件循环进程。
-
symfony/websocket-server在 6.0 中尚未发布(最早出现在 6.4+ 的实验性包中,且不推荐生产使用) - 试图用
stream_socket_server()+ReactPHP在控制器里启动服务会导致阻塞主进程,Apache/Nginx 会超时断连 -
php bin/console server:run不支持 WebSocket 握手(HTTP Upgrade 头被中间件过滤或忽略)
推荐组合:Ratchet + Symfony Messenger + Redis
这是目前 Symfony 6.0 生产环境最可控的方案:Ratchet 提供 WebSocket 连接管理,Symfony 负责业务消息构造与路由,Redis 作为消息中转与广播通道。
- Ratchet 的
WampServer支持 WAMP v1/v2 协议,兼容主流前端库(如wampy.js) - 用
symfony/messenger将业务事件(如订单创建)投递到asynctransport,再由自定义WebSocketMessageHandler推送至 Ratchet 的ConnectionInterface - 连接状态必须存 Redis(不能用 PHP 内存数组),否则多 worker 下广播失效;键名建议用
ws:connections:{client_id} - 避免在 Ratchet 的
onOpen()中调用 Doctrine EntityManager —— 它不是 Symfony 请求上下文,DB 连接未初始化,会抛ORMException: Entity manager is closed
关键代码片段:如何让 Symfony 消息触发 WebSocket 广播
不是在 Controller 里直接 $connection->send(),而是通过 Messenger 解耦:
// src/Message/OrderCreatedNotification.php
class OrderCreatedNotification
{
public function __construct(public int $orderId, public string $channel = 'orders')
{
}
}
配置 Messenger 使用 Redis transport:
# config/packages/messenger.yaml
framework:
messenger:
transports:
async: 'redis://localhost:6379/messages'
routing:
'App\Message\OrderCreatedNotification': async
编写 handler,从 Redis 拉取消息后推送给所有订阅该 channel 的客户端:
// src/MessageHandler/OrderCreatedNotificationHandler.php
class OrderCreatedNotificationHandler implements MessageHandlerInterface
{
public function __construct(
private ConnectionInterface $websocketConnection,
private RedisInterface $redis
) {}
<pre class="brush:php;toolbar:false;">public function __invoke(OrderCreatedNotification $message): void
{
// 注意:这里 $this->websocketConnection 是单例,实际需从 Redis 获取活跃连接列表
$connections = json_decode($this->redis->get('ws:active_connections') ?: '[]', true);
foreach ($connections as $connId) {
$conn = $this->getConnectionById($connId); // 需自行实现连接池管理
if ($conn && $conn->resourceId) {
$conn->send(json_encode([
'event' => 'order.created',
'data' => ['id' => $message->orderId]
]));
}
}
}}
部署时最容易被忽略的三个点
本地开发能跑通,上线后断连、收不到消息、CPU 暴涨,往往卡在这几处:
- Nginx 必须显式透传 WebSocket 头:
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade";,缺一不可 - Ratchet 进程要用
supervisord或systemd管理,不能直接php bin/console app:websocket:server后台运行 —— 没有守护机制,崩溃即失联 - 如果用 Cloudflare,需关闭「WebSockets」代理开关(默认开启),否则握手阶段的
101 Switching Protocols响应会被拦截
真正难的不是写几行 $conn->send(),而是把连接生命周期、消息一致性、水平扩展这几件事串起来 —— 一旦用户量上来,连接数、Redis 内存、Ratchet event loop 的负载都会暴露出来。











