571
社区成员
发帖
与我相关
我的任务
分享即时通讯自1998年面世以来,极大的方便了人们的通信,特别是近几年的迅速发展,不管在PC端软件还是作为手机等移动端软件的热度,即时通讯软件一直是居高不下,甚至是常年霸占软件排行榜的榜首。得益于Web技术的迅速发展,网页即使通信系统应用也日益渐增,网页即使通信不同于聊天软件,它不需要客户端,直接在浏览器即可运行。
由于传统的网页聊天室采用Ajax短轮询通信技术,这种技术需要定时不断向服务端发送请求才能都得到更新的信息,而每次请求的请求头包含很少的信息却需要很长的时间去处理,这就导致了信息不及时,占用网络带宽和浪费服务器资源等问题。
本项目为了更好的结局传统的这些在线聊天室的问题,采用基与HTML5的WebSocket协议,结合Node.js来设计一个Web即使通信系统。使用WebSocket协议可以更好的实现客户端和服务端的双向通信,而相比较传统的实现方式延迟更低,性能更好。Node.js的优点是适用于高并发的场景,且适用于事实数据交互应用,用来搭建即时通讯系统的服务端是最好的选择。
本系统主要包含以下功能:
用户端的主要功能:用户登录,用户列表,私聊,图片传输,文件传输
服务端主要功能:建立连接,断开连接,监听用户请求
系统功能模块如下图:

用例图:

概要设计:
1.用户登录流程图

2.用户列表流程图

3.私聊流程图

4.图片传输流程图

5.文件传输流程图

类图:

数据库设计
用户表
|
字段名 |
类型 |
描述 |
|
Accounter |
varchar |
登录账号 |
|
Passwd |
varchar |
用户名 |
|
Name |
varchar |
昵称 |
|
Sex |
varchar |
性别 |
|
Age |
varchar |
年纪 |
|
Avatar_url |
varchar |
头像连接 |
|
Friengds |
Varchar |
好友账号 |
文件表
|
字段名 |
类型 |
描述 |
|
File_id |
Varchar |
文件描述符 |
|
File_lx |
Int |
文件类型 1-文件 2-图片 3-语言 4-视频 |
|
File_url |
varchar |
文件连接地址 |
|
File_size |
int |
文件大小(格式M) |
聊天记录表
|
字段名 |
类型 |
描述 |
|
From |
Varchar |
发件人 |
|
To |
Varchar |
收件人 |
|
Time |
Data |
发送时间 |
|
Tex |
Varchar |
文本内容 |
|
File_id |
varchar |
包含文件的描述符 |
设计模式
由于该项目由前端为vue.js开发的,故使用了vue.js的MVVM模型。
MVVM模型
MVVM是Model-View-ViewModel的缩写,它是一种基于前端开发的架构模式,其核心是提供对View和ViewModel的双向数据绑定,这使得ViewModel的状态改变可以自动传递给View,即所谓的数据双向绑定。
Vue.js 可以说是MVVM 架构的最佳实践,专注于 MVVM 中的 ViewModel,不仅做到了数据双向绑定,而且也是一款相对来比较轻量级的JS 库,API 简洁,很容易上手。Vue.js 是采用 Object.defineProperty 的 getter 和 setter,并结合观察者模式来实现数据绑定的。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

Observer 数据监听器:能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者,内部采用Object.defineProperty的getter和setter来实现。
Compile 指令解析器:它的作用对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
Watcher 订阅者:作为连接 Observer 和 Compile 的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数。
Dep 消息订阅器:内部维护了一个数组,用来收集订阅者(Watcher),数据变动触发notify 函数,再调用订阅者的 update 方法。
从图中可以看出,当执行 new Vue() 时,Vue 就进入了初始化阶段,一方面Vue 会遍历 data 选项中的属性,并用 Object.defineProperty 将它们转为 getter/setter,实现数据变化监听功能;另一方面,Vue 的指令编译器Compile 对元素节点的指令进行扫描和解析,初始化视图,并订阅Watcher 来更新视图, 此时Wather 会将自己添加到消息订阅器中(Dep),初始化完毕。
当数据发生变化时,Observer 中的 setter 方法被触发,setter 会立即调用Dep.notify(),Dep 开始遍历所有的订阅者,并调用订阅者的 update 方法,订阅者收到通知后对视图进行相应的更新。
首先启动项目:

分别在两个浏览器打开打开http://127.0.0.1:3000即进入项目聊天室

serve.js文件:
var express = require('express');
var fs = require('fs');
var app = express();
var WebSocket = require('ws');
var WebSocketServer = WebSocket.Server,
wss = new WebSocketServer({ port: 8181, host: '127.0.0.1'});
//wss = new WebSocket("ws://192.168.13.77:8181");
var uuid = require('node-uuid');
var clients = [];
// 定义模板引擎
app.engine('html', function (filePath, options, callback) {
fs.readFile(filePath, function (err, content) {
if (err) return callback(new Error(err));
var rendered = content.toString().replace('#title#', '<title>'+ options.title +'</title>');
return callback(null, rendered);
})
});
// 指定视图所在的位置
app.set('views', './views');
// 注册模板引擎
app.set('view engine', 'html');
app.use(express.static('./asset'));
app.get('/', function (req, res) {
res.render('index', { title: '聊天室'});
})
var server = app.listen(3000, '127.0.0.1', function () {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
function wsSend(type, client_uuid, nickname, message) {
for (var i = 0; i < clients.length; i++) {
var clientSocket = clients[i].ws;
if (clientSocket.readyState === WebSocket.OPEN) {
clientSocket.send(JSON.stringify({
"type": type,
"id": client_uuid,
"nickname": nickname,
"message": message
}));
}
}
}
var clientIndex = 1;
wss.on('connection', function(ws) {
var client_uuid = uuid.v4();
var nickname = "游客" + clientIndex;
clientIndex += 1;
clients.push({ "id": client_uuid, "ws": ws, "nickname": nickname });
console.log('client [%s] connected', client_uuid);
var connect_message = nickname + " 加入聊天室";
wsSend("notification", client_uuid, nickname, connect_message);
console.log('client [%s] connected', client_uuid);
ws.on('message', function(message) {
if (message.indexOf('/nick') === 0) {
var nickname_array = message.split(' ');
if (nickname_array.length >= 2) {
var old_nickname = nickname;
nickname = nickname_array[1];
var nickname_message = "Client " + old_nickname + " changed to " + nickname;
wsSend("nick_update", client_uuid, nickname, nickname_message);
}
} else {
wsSend("message", client_uuid, nickname, message);
}
});
var closeSocket = function(customMessage) {
for (var i = 0; i < clients.length; i++) {
if (clients[i].id == client_uuid) {
var disconnect_message;
if (customMessage) {
disconnect_message = customMessage;
} else {
disconnect_message = nickname + " has disconnected";
}
wsSend("notification", client_uuid, nickname, disconnect_message);
clients.splice(i, 1);
}
}
};
ws.on('close', function () {
closeSocket();
});
process.on('SIGINT', function () {
console.log("Closing things");
closeSocket('Server has disconnected');
process.exit();
});
});
此文件代码为项目的总配置文件,规定了路由,地址,socket通信接口等。
不同的浏览器设备会根据登录时间不同,默认给用户注册唯一的用户名:

使用Socket进行网络通信,互发信息:

学号:NP+314(LRY)