avatar

mdo

Hello

  • 首页
  • 知识库
  • 归档
  • 标签
  • 关于
主页 think-swoole WebSocket 的核心步骤与代码实现
文章

think-swoole WebSocket 的核心步骤与代码实现

发表于 2026-06-16 更新于 2026-06- 16
作者 mdo
26~33 分钟 阅读

在 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"}])时:

  1. think-swoole 会截获这个数据包。

  2. 它会优先去 config/swoole.php 的 websocket.listen 中寻找对应的事件键名。

  3. 找到后,直接实例化并调用对应的监听类。

如果你把所有 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 进程去监听并推送。

代码实现:

  1. 在常规控制器中触发事件:

    php

    // app/controller/Order.php
    public function create() {
        // 下单逻辑...
    
        // 触发全局事件,通知有新订单
        event('OrderPlaced', ['order_id' => 10023, 'amount' => 99.0]);
        return json(['status' => 'success']);
    }
    

    请谨慎使用此类代码。

  2. 在 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 中挂载监听。

技术
许可协议:  CC BY 4.0
分享

相关文章

7月 26, 2026

完美地解决 TP3 老系统数据的平滑读取

为了彻底、完美地解决 TP3 老系统数据的平滑读取,必须解决两个核心痛点: TP3 的盲猜反序列化机制(标量存原样,数组/对象存原生 serialize,同时兼容 json)。 TP3 与 TP6 的缓存前缀(Prefix)不一致。 以下是为你量身定制的终极落地解决方案,通

7月 26, 2026

thinkphp3 redis序列化和反序列化

在老系统重构迁移至 ThinkPHP 6 (TP6) 的过程中,ThinkPHP 3 (TP3) 的 Redis 序列化历史遗留问题是最核心的连环坑之一。 TP3 的 S('key', $value) 缓存方法和底层 Redis 驱动,在存储非字符串(如数组、对象)时,有一套独特的序列化行为。如果不

7月 24, 2026

Table 空间极易发生哈希冲突并溢出

既然你没有在业务中主动调用 Room,只使用了全局广播,那么这个问题就非常明确了:即使你不用房间功能,think-swoole 只要开启了 WebSocket,底层就会强行初始化并注入一个名为 Room 的驱动组件。 [1] 框架底层默认使用 table 类型来作为 Room 的存储介质。当进行全局

下一篇

RpcService.php receive 没有收到数据

上一篇

think-swoole-websocket

最近更新

  • 完美地解决 TP3 老系统数据的平滑读取
  • thinkphp3 redis序列化和反序列化
  • Table 空间极易发生哈希冲突并溢出
  • 将监控程序直接跑在云端
  • AI 驱动型 Facebook 群组关键词监控 Chrome 浏览器插件

热门标签

API CodeGeex Gitkraken Management Manticore Premiere Sublime Swoole ThinkPHP ThinkPHP5

目录

©2026 mdo. 保留部分权利。