websocket系列之私聊与广播(二)

websocket系列之私聊与广播(二)

月光魔力鸭

2020-07-10 14:13 阅读 60 喜欢 0 websocket websocket系列 广播和私聊

上一章,我们学习和了解了websocket 是什么以及初始搭建,接下来,我们继续了解,如何进行广播以及对应的私聊呢。

首先,就以uwebsockets当前提供的api来看,比较少,主要就是订阅/发布接口,那么我们如果要实现这个广播和私聊的话,就应该创建多个频道,来根据频道来进行区分私聊和不同的广播等。

接下来,我们将要实现这样一个需求,先做一个简易版本的聊天室:

用户输入用户名进行登录 展示聊天室内所有成员 聊天室内成员消息互通

思路如下:

  1. 开启一个http服务,检查当前session,没有,则需要提供用户名登录,然后存入session.
  2. 在用户登录成功后连接websocket服务器,然后默认订阅一个聊天室的topic
  3. 将订阅该聊天室的成员进行数据存储
  4. 用户发送消息,则广播到该聊天室内

那么,问题来啦..如果用户退出了(或者直接关闭浏览器了怎么办,是不是得需要更新成员信息?这个后续章节再处理,我觉的应该是心跳包可以解决吧)

页面

我们先来做一个简单的页面,大体是长这个样子的。

聊天室前端页面

一个对话框,一个当前用户列表,以及一个消息发送区域。

功能

接下来,我们要实现的功能很简单,用户进入页面,如果是第一次登陆,则需要输入名称,然后随机分配一个ID存储在本地缓存中。 然后用户登陆成功后获得当前在线的用户列表信息并展示,然后用户在底部输入内容点击发送后,当前聊天室内的所有人都可以看到该信息。

步骤

html 代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>聊天室</title>
    <link rel="stylesheet" href="index.css">
</head>
<body>
    <div class="room">
        <div class="title">聊天室----ChatRoom</div>
        <div class="container">
            <div class="left"></div>
            <div class="right"></div>
        </div>
        <div class="footer">
            <textarea name="content" id="content" placeholder="输入内容点击发送或回车"></textarea>
            <div class="btn" filter="send">消息发送</div>
        </div>
    </div>

    <script>
        //检查当前用户数据
        var user = localStorage.getItem('localUser');
        if(null == user || user == ''){
            var name = prompt('请输入您的姓名.');
            //做存储,此处没做强校验,正常应该是从服务端拿到用户信息,确保唯一的。
            var uid = +new Date();
            //随机一个头像
            var avatar = 'avatar/'+(Math.ceil(Math.random()*39))+'.jpg';
            user = {name : name,id : uid,avatar : avatar};
            var str = JSON.stringify(user);
            localStorage.setItem('localUser',str);
        }
        user = typeof user == 'object' ? user : JSON.parse(user);

        //事件绑定
        var eventMap = {
            sayHi : function(dom){
                alert('3')
                ws.send(JSON.stringify({type : 'hello',content : '你好'}))
            },
            formatMsg : function(type,content){
                return JSON.stringify({
                    name : user.name,
                    id : user.id,
                    type : type,
                    avatar : user.avatar,
                    content : content||''//此处未处理,后续还需要对内容进行处理转义
                })
            },
            //服务端发送消息处理

            //上线成功反馈
            online : function(data){
                var users = data.data;
                document.querySelector('.right').innerHTML = users.map(function(item){
                    return '<div class="user-item" id="'+item.id+'"><div class="right-avatar"><img src="'+item.avatar+'" /></div>'+item.name+'</div>';
                }).join('');
            },
            //广播消息
            tip : function(data){
                var div = document.createElement('div');
                div.innerHTML = data.msg;
                div.className ='msg-tip';
                document.querySelector('.left').appendChild(div);
            },
            //当前用户消息发送
            send : function(){
                var textarea =document.getElementById('content'); 
                var content = textarea.value;
                ws.send(eventMap.formatMsg('msg',content));
                textarea.value = '';
                textarea.focus();
            },
            //广播消息--群聊
            msg : function(data){
                //区分自己和别人
                var data = data.data;
                //对广播消息进行处理。
                var container = document.querySelector('.left');
                var div = document.createElement('div');
                div.className = 'msg-block';
                div.innerHTML = '<div class="info"><div class="info-avatar"><img src="'+data.avatar+'" /></div><div class="info-name">'+data.name+': </div></div><div class="msg-content">'+data.content+'</div>';
                container.appendChild(div);
                //滚动条移动到最后
                container.scrollTop = container.scrollHeight;
            }
        };
        document.querySelectorAll('[filter]').forEach((item)=>{
            var eventType = item.getAttribute('filter');
            item.onclick = eventMap[eventType];
        });
        //websocket 处理
        var ws = new WebSocket("ws://localhost:9001");
        ws.onopen = function(evt) { 
            console.log("Connection open ..."); 
            //链接成功后,发送消息告知服务端用户上线
            ws.send(eventMap.formatMsg('online'));
        };

        ws.onmessage = function(evt) {
            var data = evt.data;
            //接受到服务端的消息,并对消息进行分类处理
            data = JSON.parse(data);
            console.log(data);
            eventMap[data.event].call(null,data);
        };

        ws.onclose = function(evt) {
            console.log("Connection closed.");
        };      
    </script>
</body>
</html>
css 样式
html,body,.room{
    margin:0px;
    padding:0px;
    height:100%;
    width:100%;
    overflow:hidden;
}
.room{
    display:flex;
    flex-direction: column;
}
.title{
    height:50px;
    text-align:center;
    font-size:20px;
    color:#333;
    border-bottom:1px solid #999;
    display:flex;
    align-items: center;
    justify-content: center;
}
.footer{
    height:100px;
    display:flex;
    flex-direction: row;
    align-items: center;
}
.btn{
    background-color: #f60;
    color:white;
    height:100px;
    line-height:100px;
    cursor:pointer;
    padding:0px 30px;
}
.footer textarea{
    flex-grow: 1;
    height:100px;
    outline:none;
    border:1px solid #ccc;
    resize: none;
    padding:10px;
    box-sizing:border-box;
}
.container{
    flex-grow:1;
    display: flex;
    flex-direction: row;
    height:100%;
    overflow-y:auto;
}
.left{
    flex:8;
    overflow-y:auto;
}
.right{
    flex:2;
    border-left:1px solid #aaa;
    overflow-y:auto;
}
.user-item{
    height:45px;
    line-height:45px;
    padding-left:50px;
    border-bottom:1px dashed #ccc;
    display:flex;
    flex-direction: row;
    font-size:30px;
}
.user-item img{
    height:35px;
    margin-right:5px;
}
.msg-tip{
    text-align:center;
    font-size:14px;
    color:#aaa;
    margin:10px 0px;
}
/**消息展示**/
.msg-block{
    display:flex;
    flex-direction: row;
    margin-bottom:10px;
    padding:0px 15px;
}
.msg-block .info{
    display:flex;
    flex-direction: row;
    align-items:center;
    font-size:20px;
}
.info-avatar{
    display:flex;
    flex-direction: row;
    align-items: center;
}
.msg-block .info .info-avatar img{
    height:20px;
}
.msg-content{
    background-color:rgba(0,0,0,0.65);
    color:white;
    padding:10px;
    margin-left:15px;
    margin-right:20px;
    border-radius:5px;
}
server 端事件处理

/***
 * 针对消息进行处理
 */

let users = [];//当前用户信息

const msgMap = {
    //用户上线
    online : (ws,data)=>{
        console.log(data);
        //检查users 是否存在重复
        let prevUser = users.find(item=>item.id == data.id);
        if(!prevUser){
            users.push(data);
        }
        //广播当前用户上线
        ws.publish('broadcast',JSON.stringify({event : 'tip',msg : '用户:['+data.name+']已加入该房间'}));
        //回馈消息,并告知当前所有用户数据
        ws.send(JSON.stringify({event : 'online',success : true,msg : '上线成功',data : users}));
    },
    //接收到用户发送的消息
    msg : (ws,data)=>{
        //收到消息进行广播,如果后续保留历史记录,此处还需要做处理
        ws.publish('broadcast',JSON.stringify({event : 'msg',data : data}))
    },
    default : (ws,data)=>{
        ws.send('hello , this is ws server');
    },
    hello : (ws,data)=>{
        ws.publish('broadcast','hi',false);
    }
};
module.exports = function(content,ws){
    try{
        let obj = JSON.parse(content);
        msgMap[obj.type].call(null,ws,obj);
    }catch(e){
        msgMap['default'].call(null,ws,content);
    }

}

完成后,新开两个浏览器,加入聊天室,就可以很嗨皮的畅聊了...

但是.. 功能是不是感觉太简单了,如果我想跟某人私聊怎么办?能不能知道某用户是否还在线?是否可以发图片啊、表情啊?

接下来,我们继续实现以下功能:

  • 增加心跳包,判定用户在线状态
  • 增加私聊
  • 增加消息内容格式

后续还将会继续实现以下功能,由于博主是做教育行业的,所以准备做个画板功能..

  • 实现教师端和学生端
  • 实现教师通过canvas画板进行操作后,由学生进行查看
  • 实现教师指定学生进行操作画板
  • 实现教师选择图片/视频/文档进行播放,由学生观看。

我将逐一实现以上功能,最后会提供每一章节的源代码,供大家下载参考。

转载请注明出处: https://chrunlee.cn/article/websocket-learn-day2.html


感谢支持!

赞赏支持
提交评论
评论信息(请文明评论)
暂无评论,快来快来写想法...
推荐
昨天客户发现了个小BUG,文章发布使用的ueditor, 上传视频的时候当时好好的,后来怎么就是播放不了?
最近一直琢磨着做一个第三方统一登录的这么一个小东西,虽然网上其实也挺多的.. 不过造轮子的感觉还是很爽的。 QQ /Github 比较简单,申请下就OK 了.. 微信真不是个东西,得花钱。
之前看知乎相中了一个想法,给宝宝做一个站,上传生活的日常照片啊、视频之类的,存储肯定是在自己家里,然后做个穿透这样..开始做的时候又想着是时候接触下这些东西了,虽然公司都一点不用,但是自己没事接触下拓展下也是好的。
本来是想做一个图床,然后在chrome扩展中添加上,但是发现chrome的扩展有点毫无头绪,只能从头练习,从头学了。
扩展程序中会有需要请求外部接口获取数据的时候,如果直接在JS中写的话,会跨域,但是谷歌扩展程序支持这种情况,只需要配置一下即可。
在程序中,会有需要后台一直运行的场景。这一节我们来做一个监控某网站的运行状态,如果没有运行,则改变当前的图标。
登录方式现在非常多,不过像我这种小站让用户自己注册基本是不可能的了,只能依赖现有的第三方的登录来集成,之前有集成github,不过这个太过针对性,这里准备集成QQ互联登录,慢慢记录下。
通过frp做穿透实现https 访问本地http项目。