视频 | 一步步教你操作websocket通知案例

                             (文末有福利)

websocket通知流程 解析

  1. 我们不能保证用户B和用户A都处于连接状态,但是通常情况下,用户B至少是连接状态,用户A不一定跟server保持连接;

  2. 任一用户都不止对应一个客户端。换言之,用户A和用户B都可能打开了多个tab页,对于一个tab页,就会有一个独立的fd标识,我们这里认为任一用户只有最新的fd有效,或者你可以认为用户所有的tab页的连接都有效;

  3. 因为没有用户系统,我们以get传递的参数uid为标识,uid=100视为用户A,uid=101视为用户B;

  4. 我们模拟的tab页包将会包含一个输入内容的文本框、一个输入目标uid的input和一个发送的按钮以满足需求。

操作流程分析:

  1. 用户A($_GET['uid'] = 100)在某个tab页的输入框输入"回复xxx的内容"字样后,点击发送

  2. 用户B($_GET['uid'] = 101)如果处于连接状态,则alert提醒用户B,他的评论被回复了

Server

后端启动:php server.php

class CommentServer
{
    private $_serv;
    public $key = '^manks.top&swoole$';
    // 用户id和fd对应的映射,key => value,key是用户的uid,value是用户的fd
    public $user2fd = [];


    public function __construct()
{
        $this->_serv = new swoole_websocket_server("127.0.0.1", 9501);
        $this->_serv->set([
            'worker_num' => 1,
            'heartbeat_check_interval' => 60,
            'heartbeat_idle_time' => 125,
        ]);
        $this->_serv->on('open', [$this, 'onOpen']);
        $this->_serv->on('message', [$this, 'onMessage']);
        $this->_serv->on('close', [$this, 'onClose']);
    }


    /**
     * @param $serv
     * @param $request
     * @return mixed
     */
    public function onOpen($serv, $request)
{
        // 连接授权
        $accessResult = $this->checkAccess($serv, $request);
        if (!$accessResult) {
            return false;
        }
        // 始终把用户最新的fd跟uid映射在一起
        if (array_key_exists($request->get['uid'], $this->user2fd)) {
            $existFd = $this->user2fd[$request->get['uid']];
            $this->close($existFd, 'uid exists.');
            $this->user2fd[$request->get['uid']] = $request->fd;
            return false;
        } else {
            $this->user2fd[$request->get['uid']] = $request->fd;
        }
    }


    /**
     * @param $serv
     * @param $frame
     * @return mixed
     */
    public function onMessage($serv, $frame)
{
        // 校验数据的有效性,我们认为数据被`json_decode`处理之后是数组并且数组的`event`项非空才是有效数据
        // 非有效数据,关闭该连接
        $data = $frame->data;
        $data = json_decode($data, true);
        if (!$data || !is_array($data) || empty($data['event'])) {
            $this->close($frame->fd, 'data format invalidate.');
            return false;
        }
        // 根据数据的`event`项,判断要做什么,`event`映射到当前类具体的某一个方法,方法不存在则关闭连接
        $method = $data['event'];
        if (!method_exists($this, $method)) {
            $this->close($frame->fd, 'event is not exists.');
            return false;
        }
        $this->$method($frame->fd, $data);
    }
    public function onClose($serv, $fd)
{
        echo "client {$fd} closed.\n";
    }


    /**
     * 校验客户端连接的合法性,无效的连接不允许连接
     * @param $serv
     * @param $request
     * @return mixed
     */
    public function checkAccess($serv, $request)
{
        // get不存在或者uid和token有一项不存在,关闭当前连接
        if (!isset($request->get) || !isset($request->get['uid']) || !isset($request->get['token'])) {
            $this->close($request->fd, 'access faild.');
            return false;
        }
        $uid = $request->get['uid'];
        $token = $request->get['token'];
        // 校验token是否正确,无效关闭连接
        if (md5(md5($uid) . $this->key) != $token) {
            $this->close($request->fd, 'token invalidate.');
            return false;
        }
        return true;
    }


    /**
     * @param $fd
     * @param $message
     * 关闭$fd的连接,并删除该用户的映射
     */
    public function close($fd, $message = '')
{
        // 关闭连接
        $this->_serv->close($fd);
        // 删除映射关系
        if ($uid = array_search($fd, $this->user2fd)) {
            unset($this->user2fd[$uid]);
        }
    }


    public function alertTip($fd, $data)
{
        // 推送目标用户的uid非真或者该uid尚无保存的映射fd,关闭连接
        if (empty($data['toUid']) || !array_key_exists($data['toUid'], $this->user2fd)) {
            $this->close($fd);
            return false;
        }
        $this->push($this->user2fd[$data['toUid']], ['event' => $data['event'], 'msg' => '收到一条新的回复.']);
    }
    /**
     * @param $fd
     * @param $message
     */
    public function push($fd, $message)
{
        if (!is_array($message)) {
            $message = [$message];
        }
        $message = json_encode($message);
        // push失败,close
        if ($this->_serv->push($fd, $message) == false) {
            $this->close($fd);
        }
    }


    public function start()
{
        $this->_serv->start();
    }
}


$server = new CommentServer;
$server->start();

前端页面 client.php

<div>
    发送内容:<textarea name="content" id="content" cols="30" rows="10"></textarea><br>
    发送给谁:<input type="text" name="toUid" value="" id="toUid"><br>
    <button onclick="send();">发送</button>
</div>


<script>
    var ws = new WebSocket("ws://127.0.0.1:9501?uid=<?php echo $uid ?>&token=<?php echo $token; ?>");
    ws.onopen = function(event) {
    };
    ws.onmessage = function(event) {
        var data = event.data;
        data = eval("("+data+")");
        if (data.event == 'alertTip') {
            alert(data.msg);
        }
    };
    ws.onclose = function(event) {
        console.log('Client has closed.\n');
    };


    function send() {
        var obj = document.getElementById('content');
        var content = obj.value;
        var toUid = document.getElementById('toUid').value;
        ws.send('{"event":"alertTip", "toUid": '+toUid+'}');
    }
</script>

一步一步教你操作

回复关键字获取资源链接:

wokerman实战之PHP在线客服:wokerman

posted @ 2020-03-11 08:00  码农编程进阶笔记  阅读(63)  评论(0编辑  收藏  举报
返回顶部 有事您Q我