avatar

mdo

Hello

  • 首页
  • 知识库
  • 归档
  • 标签
  • 关于
主页 Swoole 的底层特性完成 WebSocket 的握手控制、连接鉴权
文章

Swoole 的底层特性完成 WebSocket 的握手控制、连接鉴权

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

在 think-swoole 架构中,底层的 HTTP 协议升级(即握手过程)已经被框架封装,开发者通常不需要(也不建议)去重写底层的 onHandshake 回调,而是通过框架提供的**事件系统(Event)**来拦截和处理业务。 [1, 2]


一、 核心:握手阶段的安全鉴权与 UID 绑定 [1]

在标准的实时应用(如聊天室、通知系统)中,我们通常需要限制未登录用户连接,并将用户的 UID 与 Swoole 的客户端连接 ID (fd) 进行映射。 [1, 2, 3]

1. 前端携带 Token 发起握手 [1]

前端在发起 ws:// 握手请求时,无法像标准 HTTP 那样自定义 Headers(浏览器原生 WebSocket API 的限制)。标准的做法是**将 Token 作为 URL 参数或子协议(Subprotocol)**传给后端: [1, 2, 3, 4]

javascript

// 做法:通过 URL 查询参数将 token 发送给 TP6
const token = "your_jwt_token_here";
const ws = new WebSocket(`ws://127.0.0.1:9501?token=${token}`);

请谨慎使用此类代码。

2. 后端在 Connect 事件中做登录拦截 [1]

当 Swoole 收到客户端的握手请求时,框架会解析请求并触发 swoole.websocket.Connect 事件。此时底层连接虽然已经建立,但你可以在这里直接把非法连接直接断开。 [1, 2, 3, 4]

修改或创建你的监听器 app\listener\WsConnect.php: [1]

php

namespace app\listener;

use think\Container;
use think\swoole\Websocket;

class WsConnect
{
    public function handle($event)
    {
        // 1. $event 实际上就是底层的 Swoole\Http\Request 请求对象
        // 我们可以直接获取前端握手时带过来的 GET 参数
        $token = $event->get['token'] ?? '';
        $fd    = $event->fd; // 当前客户端的唯一 fd

        // 2. 执行你的 Token 鉴权逻辑(示例)
        $uid = $this->checkTokenAndGetUid($token);
        
        if (!$uid) {
            // 鉴权失败,直接拒绝!
            echo "客户端 {$fd} 鉴权失败,强制断开。\n";
            
            // 获取 Swoole Server 实例并强制关闭该连接
            $server = app('swoole.server');
            $server->close($fd);
            return false; 
        }

        // 3. 鉴权成功,将 UID 与当前 fd 进行关系绑定
        // 推荐做法:存入 Redis 缓存(哈希表)中
        $redis = app('redis'); 
        $redis->hSet('ws_user_fd', (string)$uid, $fd);
        $redis->hSet('ws_fd_user', (string)$fd, $uid);

        echo "用户 UID:{$uid} (fd:{$fd}) 握手鉴权成功并上线。\n";
    }

    private function checkTokenAndGetUid($token)
    {
        // 模拟解密 Token 并返回用户 id
        if ($token === 'your_jwt_token_here') {
            return 10086; 
        }
        return null;
    }
}

请谨慎使用此类代码。


二、 进阶:如何主动向指定用户发送消息?

由于用户刷新网页时 fd 会发生改变,你在业务层绝不能直接记录固定的 fd。当需要给某个用户发消息时,应当通过 UID 反查 fd。 [1]

示例:在某个普通的 Controller(如订单支付成功后)给用户推送消息: [1]

php

namespace app\controller;

use think\swoole\Websocket;

class Order
{
    public function paySuccess(Websocket $ws)
    {
        $targetUid = 10086; // 目标接收用户
        
        // 从 Redis 中反查该用户的 fd
        $redis = app('redis');
        $fd = $redis->hGet('ws_user_fd', (string)$targetUid);

        if ($fd && app('swoole.server')->exist($fd)) {
            // 使用 think-swoole 提供的 Websocket 实例直接推送到指定 fd
            $ws->to((int)$fd)->emit('notification', [
                'type' => 'order',
                'message' => '您的订单已支付成功!'
            ]);
        }
    }
}

请谨慎使用此类代码。


三、 生产环境避坑指南(Nginx 反向代理配置) [1]

在生产环境中,你的站点通常运行在 80 (HTTP) 或 443 (HTTPS) 端口下。你不可能让前端直接暴露出 Swoole 的内部端口(如 9501)。此时,必须配置 Nginx 协助完成握手代理。 [1, 2]

如果前端连接的是 wss://://yourdomain.com,Nginx 的反向代理配置应如下: [1]

nginx

location /ws {
    proxy_pass http://127.0.0.1:9501; # 指向你 TP6 Swoole 监听的端口
    proxy_http_version 1.1;         # 核心:必须使用 HTTP/1.1 才能升级协议
    
    # 核心:透传 Upgrade 和 Connection 头给 Swoole 完成握手
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    
    # 透传客户端真实 IP
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # 关键防断线配置:避免 Nginx 默认 60 秒没有数据往来就强制关闭连接
    proxy_read_timeout 600s; 
    proxy_send_timeout 600s;
}

请谨慎使用此类代码。


目前你的连接能顺利建立了吗?

技术
许可协议:  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 的存储介质。当进行全局

下一篇

websocket 握手

上一篇

ubuntu 自定义指令 执行脚本

最近更新

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

热门标签

API CodeGeex Gitkraken Management Manticore Premiere Sublime Swoole ThinkPHP ThinkPHP5

目录

©2026 mdo. 保留部分权利。