Fastadmin【Thinkphp5.0】安装使用Workerman实现websocket前后端通信,后端主动推送消息到前端

目标:实现websocket前后端通信,后端主动推送消息到前端,实现后端有数据更新时,前端页面自动更新数据。

版本composer=1.8.5、php=7.4.3nts、think-worker=1.0.*

1、安装composer 1.8.5
D:\phpstudy_pro\Extensions\composer1.8.5

2、配置全局composer
环境变量——>系统变量——>path
添加:
D:\phpstudy_pro\Extensions\composer1.8.5

3、安装php 7.4.3nts
D:\phpstudy_pro\Extensions\php\php7.4.3nts

4、配置全局php
环境变量——>系统变量——>path
添加:
D:\phpstudy_pro\Extensions\php\php7.4.3nts

5、拷贝D:\phpstudy_pro\Extensions\composer1.8.5包下
composer.bat、composer.phar
至D:\phpstudy_pro\Extensions\php\php7.4.3nts根目录
并修改composer.bat内容为:

@php "%~dp0composer.phar" %*

6、全局配置国内源

composer config -g repo.packagist composer https://packagist.phpcomposer.com

7、下载think-worker

composer require topthink/think-worker=1.0.* 

8、如需在window下做服务端,还需下载workerman

composer require workerman/workerman-for-win

9、根目录新建server.php
内容如下:

#!/usr/bin/env php
<?php
define('APP_PATH', __DIR__ . '/application/');
define('BIND_MODULE','push/Worker');
// 加载框架引导文件
require __DIR__ . '/thinkphp/start.php';

10、新建Worker.php

php think make:controller push/Worker

将里面的内容替换如下所示:

<?php

namespace app\push\controller;

use think\Db;
use think\worker\Server;
use Workerman\Lib\Timer;

class Worker extends Server
{
    protected $socket = 'websocket://0.0.0.0:2346';
    protected $snConnections = [];
    protected $heartbeat_time = '55';

    /**
     * 收到信息
     * @param $connection
     * @param $data
     */
    public function onMessage($connection, $datas)
    {
            $connection->lastMessageTime = time();
            $data = json_decode($datas);
            if (empty($data->uid)) {
                $connection->close();
                return;
            }
            $uid = 1;//这里的uid根据自己的情况去验证
            if (empty($uid)) {
                $connection->close();
                return;
            }
            switch ($data->type) {
                case 'login':
                    // 保存该用户的输送数据
                    $this->uidConnections[$uid] = $connection;
                    // $connection->send('发送成功');
                    break;
                case 'send':
                    // 发送消息
                    // $this->sendMessageByUid($uid, $datas);
                    break;
            }
    }

    /**
     * 当连接建立时触发的回调函数
     * @param $connection
     */
    public function onConnect($connection)
    {
        $connection->send('链接成功');
    }

    /**
     * 当连接断开时触发的回调函数
     * @param $connection
     */
    public function onClose($connection)
    {
        if(isset($connection->uid))
        {
            // 连接断开时删除映射
            unset($this->uidConnections[$connection->uid]);
        }
    }

    /**
     * 当客户端的连接上发生错误时触发
     * @param $connection
     * @param $code
     * @param $msg
     */
    public function onError($connection, $code, $msg)
    {
        echo "error $code $msg\n";
    }

    /**
     * 每个进程启动
     * @param $worker
     */
    public function onWorkerStart($worker)
    {
        // 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
        $inner_text_worker = new \Workerman\Worker('text://0.0.0.0:2347');
	// $inner_text_worker->reusePort=true;
        $inner_text_worker->onMessage = function ($connection, $buffer) {
            // $data数组格式,里面有uid,表示向那个uid的页面推送数据
            $data = json_decode($buffer, true);
            $uid = $data['uid'];
            // 通过workerman,向uid的页面推送数据
            $ret = $this->sendMessageByUid($uid, $buffer);
            // 返回推送结果
            $connection->send($ret ? 'ok' : 'fail');
        };
        // ## 执行监听 ##
        $inner_text_worker->listen();
        Timer::add(10, function()use($worker){
            $time_now = time();
            foreach($worker->connections as $connection) {
                // 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
                if (empty($connection->lastMessageTime)) {
                    $connection->lastMessageTime = $time_now;
                    continue;
                }
                // $diff_time = $time_now - $connection->lastMessageTime;
                // $msg = '距离上次通话已经过去'.$diff_time.'秒';
                // $connection->send($msg);
                // 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
                if ($time_now - $connection->lastMessageTime > $this->$heartbeat_time) {
                    $connection->close();
                }
            }
        });
    }

    // 向所有验证的用户推送数据
    public function broadcast($message)
    {
        foreach($this->uidConnections as $connection)
        {
            $connection->send($message);
        }
    }

    // 针对uid推送数据
    public function sendMessageByUid($uid, $message)
    {
        if(isset($this->uidConnections[$uid]))
        {
            $connection = $this->uidConnections[$uid];
            $connection->send($message);
            return true;
        }
        return false;
    }
}

后端主动推送到前端:

$client = stream_socket_client('tcp://127.0.0.1:2347', $errno, $errmsg, 1);
// 推送的数据,包含uid字段,表示是给这个uid推送
$data_sock = array('uid'=>$uid, 'type'=>'update');
// 发送数据,Text协议需要在数据末尾加上换行符
fwrite($client, json_encode($data_sock)."\n");
// 读取推送结果
// echo fread($client, 8192);

前端代码:

socketStart() {
        var uid = localStorage.getItem('token')
        var socket = new WebSocket('wss://www.test.com/wss')
        // 打开Socket
        socket.onopen = (event) => {
          socket.send(
            JSON.stringify({
              type: 'login',
              uid: uid,
            })
          )
        }
        socket.onmessage = (event) => {
          if (event.data.indexOf('update') != -1) this.fetchData() //收到更新命令,前端更新
          // console.log('receive', event.data)
        }

        // 监听Socket的关闭
        socket.onclose = (event) => {
          console.log('close', event)
          setTimeout(() => {
            this.socketStart()
          }, 50000)
        }

        socket.onerror = function (e) {
          console.log(e)
        }

        this.socket = socket
      },
      socketSend() {
        var uid = localStorage.getItem('token')
        this.socket.send(
          JSON.stringify({
            type: 'send',
            uid: uid,
          })
        )
      },

Nginx配置:

 location /wss {
        proxy_pass http://1.1.1.1:2346;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 180s;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header REMOTE-HOST $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        client_max_body_size 5000M;
    }

因为主动推送的关系,进程数设置为1:

\vendor\topthink\think-worker\src\Server.php 

protected $processes = 1;

原因:客户端1连接进程A,客户端2连接进程B,客户端2无法直接通过进程B给客户端1发送数据,因为客户端1属于进程A不属于进程B,B进程控制不到客户端1(要想两个进程之间通讯需要一些进程间通讯手段,可以使用http://doc3.workerman.net/component/channel.html)。所以所有客户端都只能连接同一个进程才能直接互相通讯,为了避免客户端连到不同进程,count设置为1。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值