vue+Node.js+WebSocket实现多人聊天室

a372929239 2022-01-15 13:30:52

     在这个学期,我学习到了一些关于前端的新知识,填补了长久以来的知识漏洞,并利用所学的知识,实现了一个简单的多人聊天室。文章接下来会先梳理一下我学到的知识,然后在对项目做具体的介绍。

一、相关知识介绍

1.Node.js

    在Node.js出现之前,JavaScript只是运行在浏览器上的语言,前端开发者可以通过JavaScript来操作dom,控制浏览器的界面。而在Node.js出现之后,JavaScript可以用在服务端了,借助JavaScript天生的事件驱动机制加V8高性能引擎,使编写高性能Web服务轻而易举。

Node.js的作者说,他创造Node.js的目的是为了实现高性能Web服务器,他首先看重的是事件机制和异步IO模型的优越性,而不是JS。但是他需要选择一种编程语言实现他的想法,这种编程语言不能自带IO功能,并且需要能良好支持事件机制。JS没有自带IO功能,天生就用于处理浏览器中的DOM事件,并且拥有一大群程序员,因此就成为了天然的选择。

 

2.Vue

    Vue是一个非常有名的前端框架,在学习Vue之前,一直是使用jQuery来进行前端的开发,这个过程还是非常接近原生js的。在学习了vue之后,借助于强大的双向绑定机制,明显感觉到开发过程更加高效了,下面我们聊一下什么是双向绑定。

    随着前端页面越来越复杂,用户对于交互性要求也越来越高,仅仅用jQuery是远远不够的,MVVM模型应运而生。MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想,在前端页面中,把Model用纯JavaScript对象表示,View负责显示,ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。像ViewModel这种操作就叫做双向绑定,它能帮助前端开发者专注于Model的变化,从而把开发者从操作DOM的繁琐步骤中解脱出来。

(MVVM示意图)

3.WebSocket

    聊天室应用中,我们希望用户能持续接收新消息,这需要和服务端建立一个长连接,而且需要服务端主动向客户端发送信息。对于Web应用来说,使用HTTP是不能完成这个任务的,因为HTTP是无状态的,无法实现长连接,同时HTTP总是被动的,无法向客户端主动发信息。如果只是基于HTTP协议来解决这个问题,无非是使用轮询等方式对服务端进行持续询问,但这样的方式都是非常消耗资源的,并不是最佳的解决方案。

    通过使用WebSocket协议可以来解决上面的问题。WebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,WebSocket的请求协议和普通的HTTP协议有一些不同,在请求头中告知服务端建立WebSocket的请求,并将协议版本等相关信息写在请求头中。随后,服务器如果接受该请求,就会返回对应的相应。之后,一个WebSocket连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。

    处理以上的这三种技术,项目中还使用了一些数据库、web框架等相关技术,但不是本文的重点,在使用时会进行简要说明。

二、项目具体介绍

1.设计总体思路

    后端开启两个端口。一个端口使用HTTP协议来处理登录请求,用户的账号密码等信息存储在数据库中,只有用户账号密码输入正确才允许建立WebSocket请求;另一个端口用于处理WebSocket请求,新连接建立时广播给所有连接告知有新用户加入,收到新的聊天消息时广播给所有连接告知有新消息。

    前端使用Vue绑定两个数据,一个是在线用户列表,另一个是消息列表,每当服务器发送新的数据时要维护这两个列表,借助Vue就能将变化显示给用户。系统的总体架构如下图所示。

(架构示意图)

 

2.具体实现细节与代码

    首先展示后端部分的实现。本项目使用的数据库是mongoDB,它是一个NoSQL数据库系统,是一个轻量级的数据库,用在这种小项目上非常合适。在js代码中,需要使用Mongoose来对数据库进行操作,它封装了mongoDB对文档的一些增删改查等常用方法,让Node.js操作mongoDB数据库变得更加容易。以下代码演示了Mongoose操作数据库,查找符合条件的用户的代码,查找成功后,会执行用户传入的callback函数,直接对数据进行操作。项目中还实现了更新用户状态的接口,和下面代码类似,不再进行单独演示。

function getUser(condition,callback){
    var userModel = db.model('userSchema', userSchema);
    userModel.find(condition,function (err,users) {
        if (err) {
            console.log('获取用户失败' + err);
            return [];
        }
        else{
            callback(users);
        }         
    });
}

登录处理的部分使用的是express框架,express是基于 Node.js平台的快速、极简的 Web 开发框架,利用它只需要几行代码就可以建立一个HTTP服务器。下面的代码演示了express处理登录请求的操作,从请求中得到账号后向数据库中查找对应的密码,判断密码是否相同来判定是否登录成功。

app.post('/login', jsonParser,(req, res) => {
    var account = req.body.account;
    var password = req.body.password;

    database.getUser({account: account}, function (result) {
        if(JSON.stringify(result)=="[]"){
            res.json("fail");
            return;
        }
        else{
            if(result[0].password==password){
                res.json("success");  
            }
            else{
                res.json("fail");
            }
            return;
        }
    });
});

WebSocket部分在后端使用的是nodejs-websocket,下面将展示服务端收到信息的处理代码,可以看到当服务端收到信息时,首先判断是登录消息还是发送新聊天的消息,然后将这样的信息通过broadcast函数向所有已建立的连接进行广播。代码中socketToId这个数据结构是一个哈希表,它的作用是当某个socket断开连接时,知道是哪个用户下线了,从而将下线消息广播,更新在线列表。

socket.on('text', function (str) {
    var msgObj = JSON.parse(str);
    switch (msgObj.type) {
        case 'login': {//把在线列表返回给客户端初始化 
            database.getUser({ isOnline: 'true' }, function (result) {
                userList = result;
                userList.push({ account: msgObj.userName });
                //数据库中更新状态   
                database.userUpdateOnline(msgObj.userName, 'true');
                //广播新的用户列表
                var loginMsgObj = {
                    type: "userList",
                    userList: userList
                }
                //记录进入hash
                socketToId.push({ socket: socket, account: msgObj.userName });
                broadcast(JSON.stringify(loginMsgObj));
            });
            break;
        }
        case 'newMsg': {//服务器收到新的消息,告诉所有客户端
            var newMsgRespObj = {
                type: 'newMsgResp',
                newMsg: msgObj.newMsg
            }
            broadcast(JSON.stringify(newMsgRespObj));
            break;
        }
    }
});

前端整体使用了Muse-UI对页面进行开发。Muse-UI是基于Vue 2.0的UI组件库,它提供了许多组件帮助开发者快速搭建页面。

使用axios向服务端发送登录请求,代码如下。

this.$axios
    .post(loginPath, {
        account: this.account,
        password: this.password,
    })
    .then(function (response) {
        if(response.data=='fail'){
            alert('登陆失败');
            return;
        }
        router.push({ path: "chat/?userName=" + account });//跳转到聊天页面
    });

发送请求的代码比较简单,需要注意两个地方,其一是当服务器认证登录成功后,用vue-router来将页面转移到聊天界面,其二是上面代码的account和password已经通过Vue进行了双向绑定,代码如下,可以看到这里起到绑定作用的是v-model属性,通过这个属性来明确它和哪个量进行了绑定。

<mu-form-item prop="input" label="账号">
    <mu-text-field v-model="account"></mu-text-field>
</mu-form-item>

<mu-form-item prop="password" label="密码">
    <mu-text-field type="password" v-model="password"></mu-text-field>
</mu-form-item>

在聊天界面使用的是原生的WebSocket来与服务端进行连接,之所以不使用其他的封装过的WebSocket是因为它们总是会带来其他的小错误,比如数据格式不能正确传递或者出现跨域等问题,这些问题大概和它们不同的封装方式有关,由于我经验不足,不能解决这些奇怪的问题,自然是哪个行得通就用哪个了。

下面代码展示客户端收到消息后如何进行处理。可以看到处理的方式是比较简单的,首先判断消息类型,然后将收到的信息添加到对应变量里,和登录时一样,由于这些变量经过了双向绑定,我们无需再做额外的动作就可以让这些改变渲染到浏览器上。

getMessage: function (msg) {
      var msgObj = JSON.parse(msg.data);
      
      switch (msgObj.type) {
        case "userList":
          this.userList = msgObj.userList;
          break;
        case "newMsgResp":{
            this.messageList.push(msgObj.newMsg);
            break;
        }
        default:
          break;
      }
}

3.部署方式和实现效果

    后端部署:node index.js

    前端部署:npm run serve  这里相当于借助node开启了一个静态资源服务器

    实现的结果如下图所示。左侧维护一个在线列表,在右侧显示聊天内容,下方可以输入新的对话,点击发送可以发送给所有的在线用户

 

三、总结

    通过这个项目简单了解了Vue这样的前端框架的基本原理,了解了MVVM模式的意义,了解了WebSocket协议的基础应用。

作者:NP613

...全文
68 回复 点赞 打赏 收藏 举报
写回复
回复
切换为时间正序
请发表友善的回复…
发表回复
相关推荐
发帖
代码中的软件工程
加入

260

社区成员

软件工程教学新范式,强化专项技能训练+基于项目的学习PBL。Git仓库:https://gitcode.net/weixin_43549265/se
帖子事件
创建了帖子
2022-01-15 13:30
社区公告
暂无公告