Workerman实现 WSS 基于客户端 ID 的精准推送

Workerman实现 WSS 基于客户端 ID 的精准推送,核心思路是:在服务端建立「客户端ID ↔ 连接实例」的映射关系,推送时通过 ID 找到对应的连接,再发送消息。下面结合 Workerman(你当前使用的框架)给出完整、可落地的实现方案:

1. 核心原理(先理解逻辑)

客户端连接WSS
服务端分配唯一connection_id
客户端主动发送自定义ID
服务端建立ID-connection映射
需要精准推送
根据自定义ID查映射表
找到对应connection实例
调用send方法推送消息

关键是:Workerman 每个连接自带唯一 connection_id,但这个 ID 是临时的(断开重连会变),所以需要绑定你业务侧的自定义客户端ID(比如用户ID、设备ID)。

2. 完整实现代码(Workerman 端)

以下是 GatewayWorker 架构下的核心代码(适配你的 8282 端口 WSS 服务):

步骤1:修改 Events.php(核心逻辑)
php 复制代码
<?php
/**
 * GatewayWorker的事件处理类
 */
class Events
{
    // 存储「客户端ID → 连接connection」的映射(全局数组)
    public static $client_map = [];

    /**
     * 当客户端连接上来时触发
     * @param $connection 连接实例
     */
    public static function onConnect($connection)
    {
        // 1. 连接成功时,Workerman自动生成唯一connection_id(临时)
        echo "客户端连接成功,临时ID:{$connection->id}\n";
    }

    /**
     * 当客户端发送消息时触发(核心:绑定自定义ID)
     * @param $connection 连接实例
     * @param $data 客户端发送的消息(JSON格式)
     */
    public static function onMessage($connection, $data)
    {
        $data = json_decode($data, true);
        if (!$data) {
            $connection->send(json_encode(['code' => 400, 'msg' => '消息格式错误']));
            return;
        }

        // 场景1:客户端绑定自定义ID(比如用户ID)
        if ($data['type'] === 'bind') {
            $client_id = $data['client_id']; // 客户端传的业务ID(如用户ID 1001)
            // ① 移除旧映射(防止同一ID多端登录覆盖)
            if (isset(self::$client_map[$client_id])) {
                unset(self::$client_map[$client_id]);
            }
            // ② 绑定新映射(自定义ID ↔ 连接实例)
            self::$client_map[$client_id] = $connection;
            // ③ 给connection绑定反向属性(方便后续操作)
            $connection->client_id = $client_id;
            // 回复客户端绑定成功
            $connection->send(json_encode(['code' => 200, 'msg' => 'ID绑定成功']));
            return;
        }

        // 场景2:精准推送指令(比如管理员后台触发)
        if ($data['type'] === 'push' && $data['target_id'] && $data['msg']) {
            $target_id = $data['target_id']; // 要推送的目标客户端ID
            $msg = $data['msg']; // 推送的内容

            // ① 检查目标ID是否存在映射
            if (!isset(self::$client_map[$target_id])) {
                $connection->send(json_encode(['code' => 404, 'msg' => '目标客户端未连接']));
                return;
            }

            // ② 精准推送:找到目标连接,发送消息
            $target_conn = self::$client_map[$target_id];
            $target_conn->send(json_encode([
                'type' => 'push',
                'from' => $connection->client_id ?? 'admin',
                'msg'  => $msg
            ]));

            // ③ 回复推送方成功
            $connection->send(json_encode(['code' => 200, 'msg' => '精准推送成功']));
            return;
        }

        // 其他消息处理...
        $connection->send(json_encode(['code' => 200, 'msg' => '收到消息:'.$data['msg']]));
    }

    /**
     * 当客户端断开连接时触发(清理映射,避免内存泄漏)
     * @param $connection 连接实例
     */
    public static function onClose($connection)
    {
        // 移除该连接对应的ID映射
        if (isset($connection->client_id) && isset(self::$client_map[$connection->client_id])) {
            unset(self::$client_map[$connection->client_id]);
            echo "客户端 {$connection->client_id} 断开连接,已清理映射\n";
        }
        echo "临时ID {$connection->id} 断开连接\n";
    }
}
步骤2:客户端对接(浏览器/JS 示例)
javascript 复制代码
// 连接WSS服务(用域名,避免证书报错)
const ws = new WebSocket('wss://你的域名:8282');

// 1. 连接成功后,绑定自定义ID(比如用户ID 1001)
ws.onopen = function() {
    console.log('连接成功');
    // 发送绑定指令
    ws.send(JSON.stringify({
        type: 'bind',
        client_id: '1001' // 业务侧的唯一ID(用户ID/设备ID)
    }));
};

// 2. 接收服务端推送的消息
ws.onmessage = function(e) {
    const res = JSON.parse(e.data);
    console.log('收到消息:', res);
    // 处理精准推送的消息
    if (res.type === 'push') {
        alert(`收到精准推送:${res.msg}`);
    }
};

// 3. 主动触发精准推送(比如给ID 1002推送消息)
function pushToClient() {
    ws.send(JSON.stringify({
        type: 'push',
        target_id: '1002', // 要推送的目标ID
        msg: '你好,这是给你的精准推送!'
    }));
}

3. 进阶优化(生产环境必做)

(1)映射表持久化/分布式

上面的 $client_map 是内存数组,仅适用于单服务器;如果是多服务器集群,需要用 Redis 存储映射关系:

php 复制代码
// 绑定ID时存Redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->set('client:'.$client_id, $connection->id); // 存connection_id
$redis->set('connection:'.$connection->id, $client_id); // 反向映射

// 推送时查Redis
$target_conn_id = $redis->get('client:'.$target_id);
// 通过Gateway类获取对应连接
Gateway::sendToClient($target_conn_id, $msg);
(2)连接异常处理
  • 给映射表加过期时间,避免无效连接残留;
  • 推送前检查连接是否存活($connection->getStatus())。
(3)GatewayWorker 专用方法

如果用的是 GatewayWorker 完整架构,推荐用官方封装的方法:

php 复制代码
// 绑定ID
Gateway::bindUid($connection->id, $client_id);
// 根据ID推送
Gateway::sendToUid($client_id, $msg);
// 解绑ID
Gateway::unbindUid($connection->id, $client_id);

4. 测试验证步骤

  1. 客户端A连接,绑定ID 1001
  2. 客户端B连接,绑定ID 1002
  3. 客户端A调用 pushToClient()1002 推送消息;
  4. 客户端B能收到精准推送,客户端A收到"推送成功"回复 → 验证完成。

回顾

  1. 精准推送的核心是建立「业务客户端ID ↔ Workerman连接实例」的映射关系
  2. 客户端先主动绑定自定义ID,服务端存储映射,推送时通过ID查连接并发送消息;
  3. 单服务器用内存数组即可,集群环境需用Redis存储映射,结合GatewayWorker的 sendToUid 方法更高效;
  4. 断开连接时必须清理映射,避免内存泄漏和无效推送。

如果需要适配多服务器集群、或结合PHP后台(非客户端触发)实现服务端主动推送,我可以补充对应的代码示例。

相关推荐
百结2142 小时前
Nginx性能优化与监控实战
java·nginx·性能优化
jason_renyu2 小时前
Maven 新手完全使用指南(完整版)
java·maven·maven新手指南·maven新手完全使用指南·maven新手使用教程·maven教程
jolimark2 小时前
Spring Boot 集成 Kettle
java·spring boot·后端
云栖笑笑生2 小时前
Java中变量的定义及注意事项
java
牛马1112 小时前
Flutter CustomPaint
开发语言·前端·javascript
阿拉斯攀登2 小时前
第 11 篇 RK 平台安卓驱动实战 4:I2C 设备驱动开发,以 OLED 屏为例
android·驱动开发·i2c·瑞芯微·嵌入式驱动·rk3576·嵌入式安卓
玛卡巴卡ldf2 小时前
【LeetCode 手撕算法】(子串) 560-和为 K 的子数组
java·数据结构·算法·leetcode
wuxinyan1232 小时前
Java面试题45:一文深入了解Spring 事务原理
java·spring·面试·事务
重庆兔巴哥2 小时前
Java环境变量配置不成功,会有什么症状?
java·开发语言