fastadmin + workman 实现websocket前后端通信

1.安装workman扩展

composer require workerman/workerman

2.安装think-worker

composer require topthink/think-worker=1.0.* 

3.如果在windows下使用,还需下载

composer require workerman/workerman-for-win

4.根目录创建server.php 。用来启动服务

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

5.新建Worker.php 服务器要记得开发端口

<?php
 
namespace app\push\controller;
 
use think\worker\Server;
use Workerman\Lib\Timer;
use think\Log;
class Worker extends Server
{
    protected $socket = 'websocket://127.0.0.1:2346';
    protected $uidConnections = [];
    protected $HEARTBEAT_TIME = '300';
    
     public function _initialize()
    {
        Log::init(['type' => 'File','apart_level'   =>  ['API'], 'path' =>  '../runtime/log/']);
    }
    /**
     * 收到信息
     * @param $connection
     * @param $data
     */
    public function onMessage($connection, $data)
    {
        // 给connection临时设置一个lastMessageTime属性,用来记录上次收到消息的时间
        $connection->lastMessageTime = time();
        $data = json_decode($data,false);
      
        //接收到消息进行逻辑处理  修改状态
        switch($data->type){
            case 'login':
                // 保存该用户的输送数据
                $this->uidConnections[$data->uid] = $connection;
                $connection->send('所有用户等会收到的信息');
                $this->send_uid($data->uid,"通过保存uid:{$data->uid}为给你发的信息");
                break;
            case 'ping':
                // 心跳
                $connection->send('pong');
                break;
            case 'sendMessage':
                // 发送所有消息
                $this->send_all($data->message,$data->uid);
                break;
            case 'sendUser' :
                // 发送单个消息
                $this->send_uid($data->uid,$data->message);
                break;
        }
    }
 
    /**
     * 当连接建立时触发的回调函数
     * @param $connection
     */
    public function onConnect($connection)
    {
        $connection->send('已建立连接');
    }
 
    /**
     * 当连接断开时触发的回调函数
     * @param $connection
     */
    public function onClose($connection)
    {
        $connection->send('连接断开');
    }
 
    /**
     * 当客户端的连接上发生错误时触发
     * @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://127.0.0.1:5678');
        $inner_text_worker->onMessage = function ($connection, $buffer){
            $buffer = json_decode($buffer,false);
            switch($buffer->type){
                case 'sendMessage' :
                    $res = $this->send_all($buffer->message);
                    break;
                case 'sendUser' :
                    $res = $this->send_uid($buffer->uid,$buffer->message);
                    break;
                default:
                    $res = $this->send_all('1111');
            }
         $connection->send($res ? 'ok' : 'fail');
        };
        $inner_text_worker->listen();
        Timer::add($this->HEARTBEAT_TIME, 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 send_uid($uid,$message)
    {
        if(isset($this->uidConnections[$uid])){
            // 获取之前用户的链接
            $conn = $this->uidConnections[$uid];
            //echo $uid.PHP_EOL;
            $conn->send($message);
            Log::write("消息发送给 UID $uid: $message", 'API'); // 添加调试日志
            return true;
        }
         Log::write("UID $uid 不存在", 'API'); // 添加调试日志
        return false;
    }
 
    public function send_all($message,$uid = '')
    {
        foreach($this->uidConnections as $conn){
            if(!empty($uid)){
                //不推送给自己
                if($conn != $this->uidConnections[$uid]){
                    $conn->send($message);
                }
            }else{
                $conn->send($message);
            }
        }
        return true;
    }
}

6.根目录下执行命令,运行服务

php server.php start

7.新加test.html 用来接受测试

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        // 创建一个Socket实例
        var socket = new WebSocket('ws://127.0.0.1:2346');
        var uid = "33";
        //心跳间隔
        var heartbeatInterval = 290 * 1000;
        // 打开Socket
        socket.onopen = function(event) {
            // 发送一个初始化消息
            socket.send(
                JSON.stringify({
                    type: "login",
                    uid: uid,
                })
            );
             // 启动心跳定时器
             startHeartbeat();
            
        };
        socket.onmessage = function(event) {
            console.log('收到消息',event.data);
             // 处理收到的心跳回复
             if (event.data === 'pong') {
                console.log('心跳回复');
            }
 
        };
 
        // 监听Socket的关闭
        socket.onclose = function(event) {
            console.log('关闭监听',event);
            // 连接关闭后,清除心跳定时器
            stopHeartbeat();
        };
 

        // 心跳定时器
        var heartbeatTimer;

        function startHeartbeat() {
            heartbeatTimer = setInterval(function() {
                socket.send(JSON.stringify({ type: 'ping' }));
                console.log('发送心跳');
            }, heartbeatInterval);
        }

        function stopHeartbeat() {
            if (heartbeatTimer) {
                clearInterval(heartbeatTimer);
                heartbeatTimer = null;
            }
        }

        function  send()
        {
            var val = $("#msg").val();
            if(val==''){
                alert('请输入发送内容');
                return false;
            }
            socket.send(JSON.stringify({
                type: "sendMessage",
                uid: uid,
                message:val
            }));
        }
    </script>
</head>
<body>
<input type="text" id="msg">
<button onclick="send()">发送消息</button>
</body>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</html>

8.手动推送消息

 public static function push_msg($str = '',$uid = '33' )
    {
        // $uid = input('uid','33');
        $type = input('type','sendUser');
        $msg = input('msg','hello word');
        $client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
        if (!$client) {
            echo "Error: $errmsg\n";
            return false;
        }
        // 推送的数据,包含用户,表示是给这个用户推送
        //有数据则进行推送
        if($str)
        {
            $data = array('uid'=>$uid, 'message'=>$str,'type'=>$type);
            // 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符
            $bytesWritten = fwrite($client, json_encode($data) . "\n");
            
	        if ($bytesWritten === false) {
	            Log::write('Failed to write data to the socket', 'API');
	            fclose($client);
	            return false;
	        }
            stream_set_timeout($client, 1);
            // 读取推送结果
             $response = fread($client, 8192);
            fclose($client);
            return $response;
        }
        
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值