think-swoole WebSocket 的核心步骤与代码实现
在 ThinkPHP 框架中,官方提供了 topthink/think-swoole 扩展来集成 Swoole 并支持 WebSocket 功能。以下是搭建、配置和使用 think-swoole WebSocket 的核心步骤与代码实现。 [1, 2]
1. 安装与开启功能
基础安装
在项目根目录下通过 Composer 安装扩展: [1, 2]
bash
composer require topthink/think-swoole
请谨慎使用此类代码。
修改配置
生成或打开 config/swoole.php 配置文件,找到 websocket 项并开启服务: [1, 2, 3, 4]
php
'websocket' => [
'enable' => true, // 必须设为 true 开启
'handler' => \think\swoole\websocket\Handler::class,
'ping_interval' => 25000,
'ping_timeout' => 60000,
'room' => [
'type' => 'table', // 默认使用内存表记录房间/客户端关系
],
],
请谨慎使用此类代码。
2. 绑定事件监听器
WebSocket 的连接、断开和消息接收是基于 ThinkPHP 事件系统 实现的。推荐通过创建一个 事件订阅类 来统一管理。 [1]
创建订阅类
运行命令生成监听类: [1]
bash
php think make:listener SWScribe
请谨慎使用此类代码。
编辑生成的 app\listener\SWScribe.php 文件,处理核心事件: [1, 2]
php
<?php
namespace app\listener;
use think\swoole\Websocket;
use Swoole\WebSocket\Frame;
class SWScribe
{
// 当客户端连接时
public function onConnect($event, Websocket $websocket)
{
// $websocket->getSender() 获取当前客户端的 fd (连接ID)
$websocket->emit('message', '欢迎连接服务器!你的FD是: ' . $websocket->getSender());
}
// 当收到自定义事件消息时 (以 test 事件为例)
public function onTest(Websocket $websocket, $data)
{
// 回复当前客户端
$websocket->emit('message', '收到测试数据: ' . json_encode($data));
// 广播给所有人
$websocket->broadcast()->emit('message', '有人发送了新消息');
}
// 当客户端断开连接时
public function onClose(Websocket $websocket)
{
// 处理清理逻辑,如退出房间
}
// 事件注册:将方法绑定到 think-swoole 的内部事件上
public function subscribe(\think\Event $event)
{
$event->listen('swoole.websocket.Connect', [$this, 'onConnect']);
$event->listen('swoole.websocket.Close', [$this, 'onClose']);
// 自定义前端事件,格式为 swoole.websocket.YOUR_EVENT
$event->listen('swoole.websocket.test', [$this, 'onTest']);
}
}
请谨慎使用此类代码。
全局注册订阅
打开 app/event.php,在 subscribe 数组中加入该类: [1]
php
return [
'bind' => [],
'listen' => [],
'subscribe' => [
\app\listener\SWScribe::class, // 注册 WebSocket 事件订阅
],
];
请谨慎使用此类代码。
3. 注意事项:数据传输协议 (Socket.io) [1]
think-swoole 默认使用的是 Socket.io 兼容协议 解析请求。这意味着: [1, 2, 3, 4]
如果你直接使用原生浏览器前端
new WebSocket('ws://...')发送普通文本,后端可能无法正常解析事件路由(会报解析错误或无法触发具体事件)。解决方案 1 (推荐):前端使用 socket.io-client 库连接。
解决方案 2 (原生):如果你必须用原生 WebSocket,你需要修改
config/swoole.php中的parser配置,或使用第三方社区扩展(如okcoder/think-swoole-websocket)来改为纯文本/JSON 解析器。 [1, 2, 3, 4]
方案 1 前端代码示例 (Socket.io)
html
<script src="https://socket.io"></script>
<script>
// 假设 think-swoole 服务运行在 9501 端口
const socket = io('ws://127.0.0.1:9501', {
transports: ['websocket']
});
// 监听后端发来的 message 事件
socket.on('message', (data) => {
console.log('收到后端消息:', data);
});
// 向后端触发 test 事件并发送数据
socket.emit('test', { name: '张三', age: 18 });
</script>
请谨慎使用此类代码。
4. 常用服务操作指令
在控制台使用 ThinkPHP 命令行工具管理服务器: [1]
启动服务:
php think swoole start停止服务:
php think swoole stop重启服务:
php think swoole restart热重载代码:
php think swoole reload(仅重载 Worker 进程中的业务代码) [1, 2, 3, 4]
在 ThinkPHP 6 (TP6) 的 think-swoole 架构中,不需要在系统的 app/event.php 中注册全局订阅(Event Subscribe),但也不建议完全依赖它。
最标准、最清晰的做法是直接在 config/swoole.php 的 websocket.listen 配置项中进行精准绑定。
为了让你更好地理解和架构项目,以下是深度解析以及最佳实践:
1. 为什么不需要全局订阅?
think-swoole 内部实现了一套独立的 WebSocket 事件分发器。当客户端发送一个自定义事件(例如:["chat", {"msg": "hello"}])时:
think-swoole会截获这个数据包。它会优先去
config/swoole.php的websocket.listen中寻找对应的事件键名。找到后,直接实例化并调用对应的监听类。
如果你把所有 WebSocket 业务都写进全局订阅类(Subscriber),会导致所有的消息都揉在一个文件里,随着项目变大,代码会变得极难维护(出现大量的 switch-case 或 if-else)。
2. 官方推荐的最佳实践:精准事件监听 (Listener)
不要用订阅,而是为每一个具体的业务动作,创建一个独立的 Listener 类。
第一步:在 config/swoole.php 中按业务解耦配置
php
'websocket' => [
'enable' => true,
'listen' => [
// 1. 系统内置基础事件
'connect' => \app\listener\WsConnect::class,
'close' => \app\listener\WsClose::class,
// 2. 具体的业务事件,一个事件对应一个类
'room.join' => \app\listener\WsRoomJoin::class, // 加入房间
'room.leave' => \app\listener\WsRoomLeave::class, // 离开房间
'chat.send' => \app\listener\WsChatSend::class, // 发送消息
],
],
请谨慎使用此类代码。
第二步:编写单一职责的监听类
以发送聊天消息为例,创建 app/listener/WsChatSend.php:
php
namespace app\listener;
use think\swoole\Websocket;
class WsChatSend
{
/**
* TP6 会自动注入 Websocket 实例和客户端传过来的 data
*/
public function handle(Websocket $ws, $data)
{
// 这里的 $data 直接就是前端传过来的对象/数组
$roomId = $data['room_id'] ?? 0;
$content = $data['content'] ?? '';
// 广播给房间内的所有人
$ws->to('room_' . $roomId)->emit('chat.receive', [
'user' => $ws->getSender(),
'content' => $content,
]);
}
}
请谨慎使用此类代码。
3. 什么时候才需要用到“全局事件”?
虽然不用“事件订阅”,但在 Swoole 长连接开发中,有一种情况需要用到 TP6 的全局事件:跨进程通信 / 异步任务(Task)。
常见痛点场景:
你在写一个普通的 TP6 控制器(HTTP 请求),用户下单成功了,你想实时通知后台管理员(WebSocket 客户端)。
问题:常规的控制器运行在 PHP-FPM 或普通的 Swoole 进程中,拿不到 WebSocket 的连接实例(
$ws对象),无法直接发消息。解决办法:在控制器中触发一个 TP6 全局事件,让 Swoole 进程去监听并推送。
代码实现:
在常规控制器中触发事件:
php
// app/controller/Order.php public function create() { // 下单逻辑... // 触发全局事件,通知有新订单 event('OrderPlaced', ['order_id' => 10023, 'amount' => 99.0]); return json(['status' => 'success']); }请谨慎使用此类代码。
在
config/swoole.php的websocket.listen中让 Swoole 捕获它:php
'websocket' => [ 'listen' => [ // 监听全局事件,转化为 WS 推送 'OrderPlaced' => \app\listener\WsOrderNotify::class, ], ],请谨慎使用此类代码。
总结
前端发来的 WS 消息:坚决不用全局订阅。直接在
config/swoole.php里一个事件配一个 Listener,代码最干净。后端 HTTP 想找 WS 联动:利用全局事件名称(如
event('Name')),在swoole.php中挂载监听。