专栏首页码农编程进阶笔记视频 | 一步步教你操作websocket通知案例

视频 | 一步步教你操作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>

一步一步教你操作

本文分享自微信公众号 - 码农编程进阶笔记(lxw1844912514),作者:竹子

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • sync-player:使用websocket实现异地同步播放视频

    前段时间我有这样一个需求,想和一个异地的人一起看电影,先后在网上找了一些方案,不过那几个案都有一些缺点

    GoEasy消息推送
  • 从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

    IM App 是我做过 App 类型里复杂度最高的一类,里面可供深究探讨的技术难点非常之多。这篇文章和大家聊下从移动端客户端的角度所关注的IM消息可靠性和送达机...

    JackJiang
  • Netty干货分享:京东京麦的生产级TCP网关技术实践总结

    京东的京麦商家后台2014年构建网关,从HTTP网关发展到TCP网关。在2016年重构完成基于Netty4.x+Protobuf3.x实现对接PC和App上下行...

    JackJiang
  • 理论联系实际:从零理解WebSocket的通信原理、协议格式、安全性

    WebSocket的出现,使得浏览器具备了实时双向通信的能力。本文由浅入深,介绍了WebSocket如何建立连接、交换数据的细节,以及数据帧的格式。此外,还简要...

    JackJiang
  • 如何从海量用户中轻松定位H5视频播放器问题?

    一、方案背景介绍 随着互联网技术的发展,用户使用QQ浏览器进行上网观看视频越来越多,最近统计目前使用QQ浏览器观看视频已经过亿。不同的用户由于不同的场景下播放...

    腾讯移动品质中心TMQ
  • 使用flv.js做直播

    为什么要在这个时候探索flv.js做直播呢?原因在于各大浏览器厂商已经默认禁用Flash,之前常见的Flash直播方案需要用户同意使用Flash后才可以正常使用...

    IMWeb前端团队
  • SpringBoot 使用WebSocket打造在线聊天室(基于注解)

    例如:webSocket.onmessage = function (event) {console.log('WebSocket收到消息:' + event....

    好好学java
  • 一步一步教你使用AgileEAS.NET基础类库进行应用开发-WinForm应用篇-复杂业务的实现(商品入库)-附案例操作视频

    系列回顾      WinForm篇前面我用了两篇文章实例演示了一个基于AgileEAS.NET实现一个简单的增加、删除、修改、查询与打印的典型的简单应用案例,...

    魏琼东
  • IM聊天教程:发送图片/视频/语音/表情

    为此,小编特意写了一个vue版本的Demo,实现了图片视频文件和表情的的发送,参考这个Demo源代码,相信你就可以轻松的用Uniapp和小程序完成类似的功能。

    GoEasy消息推送
  • 腾讯云快速搭建微信小程序服务

    小程序后台服务需要通过 HTTPS 访问,在实验开始之前,我们要准备域名和 SSL 证书。

    云上云
  • 简单5步使用即构SDK,实现在线抓娃娃H5产品

    为了保障即构线上抓娃娃H5方案能够普遍的适配所有浏览器,即构团队在视频网络完成视频转码,把视频转成MPEG1。然后,在用户侧H5和视频网络之间加入接入服务器,把...

    BestSDK
  • 简单5步使用即构SDK,实现在线抓娃娃H5产品

    为了保障即构线上抓娃娃H5方案能够普遍的适配所有浏览器,即构团队在视频网络完成视频转码,把视频转成MPEG1。然后,在用户侧H5和视频网络之间加入接入服务器,把...

    企鹅号小编
  • 你可能不知道的浏览器实时通信方案

    这里以sockjs作为切入点,这是一个流行的浏览器实时通信库,提供了’类Websocket’、一致性、跨平台的API,旨在浏览器和服务器之间创建一个低延迟、全双...

    _sx_
  • Electron 低延迟视频流播放方案探索

    去年最后一篇文章介绍了我们的 Electron 桌面客户端的一些优化措施,这篇文章也跟我们正在开发的 Electron 客户端有一定关系。最近我们正在预研在 E...

    _sx_
  • 求帮神器——远程实时终端

    当你写代码时遇到问题了,如果大佬离你比较近,你可以拿着笔记本电脑去现场请教,如果比较远呢?你可能会视频聊天,然后让大佬遥控你,但是这样就存在一个问题,大佬需要用...

    somenzz
  • 以网游服务端的网络接入层设计为例,理解实时通信的技术挑战1、前言2、相关文章3、主流网游的网络通信架构原理4、题外话:该如何理解C/M架构?5、网络接入层的作用6、网络接入层的通信协议选择7、网络接入

    以现在主流的即时通讯应用形态来讲,一个完整的即时通讯IM应用其实是即时通信(英文简写:IM=Instant messaging)和实时通信(英文简写:RTC=R...

    JackJiang
  • 以网游服务端的网络接入层设计为例,理解实时通信的技术挑战

    以现在主流的即时通讯应用形态来讲,一个完整的即时通讯IM应用其实是即时通信(英文简写:IM=Instant messaging)和实时通信(英文简写:RTC=R...

    JackJiang
  • 基于 CentOS 搭建微信小程序服务

    小程序无疑是今年互联网的重大热点。本实验带您从零开始,基于 NodeJS 搭建起一个可以支撑小程序运行的服务,包括 HTTPS 部署、会话服务、WebSoc...

    MonroeCode
  • 微信小程序使用GoEasy实现websocket实时通讯

    不需要下载安装,便可以在微信好友、微信群之间快速的转发,用户只需要扫码或者在微信里点击,就可以立即运行,有着近似APP的用户体验,使得微信小程序成为全民热爱的好...

    GoEasy消息推送

扫码关注云+社区

领取腾讯云代金券