571
社区成员




在这个学期,我学习到了一些关于前端的新知识,填补了长久以来的知识漏洞,并利用所学的知识,实现了一个简单的多人聊天室。文章接下来会先梳理一下我学到的知识,然后在对项目做具体的介绍。
在Node.js出现之前,JavaScript只是运行在浏览器上的语言,前端开发者可以通过JavaScript来操作dom,控制浏览器的界面。而在Node.js出现之后,JavaScript可以用在服务端了,借助JavaScript天生的事件驱动机制加V8高性能引擎,使编写高性能Web服务轻而易举。
Node.js的作者说,他创造Node.js的目的是为了实现高性能Web服务器,他首先看重的是事件机制和异步IO模型的优越性,而不是JS。但是他需要选择一种编程语言实现他的想法,这种编程语言不能自带IO功能,并且需要能良好支持事件机制。JS没有自带IO功能,天生就用于处理浏览器中的DOM事件,并且拥有一大群程序员,因此就成为了天然的选择。
Vue是一个非常有名的前端框架,在学习Vue之前,一直是使用jQuery来进行前端的开发,这个过程还是非常接近原生js的。在学习了vue之后,借助于强大的双向绑定机制,明显感觉到开发过程更加高效了,下面我们聊一下什么是双向绑定。
随着前端页面越来越复杂,用户对于交互性要求也越来越高,仅仅用jQuery是远远不够的,MVVM模型应运而生。MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想,在前端页面中,把Model用纯JavaScript对象表示,View负责显示,ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。像ViewModel这种操作就叫做双向绑定,它能帮助前端开发者专注于Model的变化,从而把开发者从操作DOM的繁琐步骤中解脱出来。
(MVVM示意图)
聊天室应用中,我们希望用户能持续接收新消息,这需要和服务端建立一个长连接,而且需要服务端主动向客户端发送信息。对于Web应用来说,使用HTTP是不能完成这个任务的,因为HTTP是无状态的,无法实现长连接,同时HTTP总是被动的,无法向客户端主动发信息。如果只是基于HTTP协议来解决这个问题,无非是使用轮询等方式对服务端进行持续询问,但这样的方式都是非常消耗资源的,并不是最佳的解决方案。
通过使用WebSocket协议可以来解决上面的问题。WebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,WebSocket的请求协议和普通的HTTP协议有一些不同,在请求头中告知服务端建立WebSocket的请求,并将协议版本等相关信息写在请求头中。随后,服务器如果接受该请求,就会返回对应的相应。之后,一个WebSocket连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。
处理以上的这三种技术,项目中还使用了一些数据库、web框架等相关技术,但不是本文的重点,在使用时会进行简要说明。
后端开启两个端口。一个端口使用HTTP协议来处理登录请求,用户的账号密码等信息存储在数据库中,只有用户账号密码输入正确才允许建立WebSocket请求;另一个端口用于处理WebSocket请求,新连接建立时广播给所有连接告知有新用户加入,收到新的聊天消息时广播给所有连接告知有新消息。
前端使用Vue绑定两个数据,一个是在线用户列表,另一个是消息列表,每当服务器发送新的数据时要维护这两个列表,借助Vue就能将变化显示给用户。系统的总体架构如下图所示。
(架构示意图)
首先展示后端部分的实现。本项目使用的数据库是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;
}
}
后端部署:node index.js
前端部署:npm run serve 这里相当于借助node开启了一个静态资源服务器
实现的结果如下图所示。左侧维护一个在线列表,在右侧显示聊天内容,下方可以输入新的对话,点击发送可以发送给所有的在线用户
通过这个项目简单了解了Vue这样的前端框架的基本原理,了解了MVVM模式的意义,了解了WebSocket协议的基础应用。
作者:NP613