ThinkPHP5 利用 WebSocket 实现全站公告推送

3 min read

WebSocket是一种在单个TCP连接上进行全双工通信的协议,允许服务端主动向客户端推送数据。

最近在开发 MoeCTF 中的 公告实时推送 功能,本来想通过 轮询 实现,但是想了一下 轮询 是客户端主动向服务端发送请求,如果单个用户感觉还行,但如果在线的用户过多均通过 轮询 的方式进行请求,容易造成大量无用请求,从而导致负载过高。

所以打算使用 WebSocket 来进行一次长连接,减少请求。由于对 WebSocket 没有过多的要求,所以直接选择了基于 WorkerMan 开发的 GatewayWorker

安装

ThinkPHP5 只能安装 2.* 版本

composer require topthink/think-worker=2.0.*

Events

修改 vendor/topthink/think-worker/src/Events.php 内的 onConnectonMessage 方法

// 当有客户端连接时,将client_id返回,让mvc框架判断当前uid并执行绑定
public static function onConnect($client_id)
{
    Gateway::sendToClient($client_id, json_encode(array(
        'type'      => 'init',
        'client_id' => $client_id
    )));
}

// GatewayWorker建议不做任何业务逻辑,onMessage留空即可
public static function onMessage($client_id, $message)
{

}

运行

注意每一次修改 WorkerMan 的配置后都需要重新启动

php think worker:gateway

image-20221030184852357

控制器

当服务端收到连接请求时,判断用户是否登录,如果已经登录把 client_id 添加到群组 All

<?php
namespace app\index\controller;
use GatewayWorker\Lib\Gateway;
use think\facade\Session;
use think\Controller;

class Push extends controller
{
    public function initialize()
    {
        parent::initialize();
        if (!Session::has('uid') {
            returnJsonData(201, 'Please login first')->send();
            exit;
        }
    }
    
    public function index($data){
        $data = [
            'type' => 'notify',
            'msg'  => input('msg'),
        ];
        Gateway::sendToGroup('All', json_encode($data));
    }

    public function notify()
    {
        Gateway::$registerAddress = '127.0.0.1:1236';
        $client_id = $_POST['client_id'];
        Gateway::joinGroup($client_id, 'All');
    }
}

用户界面

ws = new WebSocket("ws://127.0.0.1:2348");
ws.onmessage = function (e) {
  var data = eval("(" + e.data + ")");
  var type = data.type || "";
  switch (type) {
    case "init":
      $.post(
        "/api/v1/notify",
        { client_id: data.client_id },
        function (data) {},
        "json"
      );
      break;
    case "notify":
      console.log('notify:' + data);
      break;
    default:
      console.log(data);
  }
};

效果

管理员通过 push 方法请求的数据会推送给所有登录用户。

image-20221030184920941