内容简介:先撸个简单版本,能够实现用户与服务器之间的通信要实现好友通信,在前后端的再者,后台要记录发送的消息,也必须要有好友表的主键 friendId,表示为这两个人之间的消息
-
websocket 在实际项目中有着很广的应用,如好友聊天,异步请求,react-hot-loader 的热更新等等
-
本文前端采用原生
WebSocket,后端采用express-ws 库实现聊天通信 -
聊天原理很简单,如下图:
简单版本
先撸个简单版本,能够实现用户与服务器之间的通信
- 前端:WsRequest 封装类
class WsRequest {
ws: WebSocket
constructor(url: string) {
this.ws = new WebSocket(url)
this.initListeners()
}
initListeners() {
this.ws.onopen = _event => {
console.log('client connect')
}
this.ws.onmessage = event => {
console.log(`来自服务器的信息:${event.data}`)
}
this.ws.onclose = _event => {
console.log('client disconnect')
}
}
send(content: string) {
this.ws.send(content)
}
close() {
this.ws.close()
}
}
// 使用
const ws = new WsRequest('your_websocket_url') // 如: ws://localhost:4000/ws
ws.send('hello from user')
复制代码
- 服务端:WsRouter 封装类,使用单例模式
import expressWs, { Application, Options } from 'express-ws';
import ws, { Data } from 'ws';
import { Server as hServer } from 'http';
import { Server as hsServer } from 'https';
class WsRouter {
static instance: WsRouter;
wsServer: expressWs.Instance;
clientMap: Map<string, ws>; // 保存所有连接的用户 id
constructor(
private path: string,
private app: Application,
private server?: hServer | hsServer,
private options?: Options
) {
this.wsServer = expressWs(this.app, this.server, this.options);
this.app.ws(this.path, this.wsMiddleWare);
this.clientMap = new Map();
}
static getInstance(path: string, app: Application, server?: hServer | hsServer, options: Options = {}) {
if (!this.instance) {
this.instance = new WsRouter(path, app, server, options);
}
return this.instance;
}
wsMiddleWare = (wServer: any, _req: any) => {
this.clientMap.set(id, wServer);
this.broadcast('hello from server'); // send data to users
wServer.on('message', async (data: Data) => {
console.log(`来自用户的信息:${data.toString()}`);
});
wServer.on('close', (closeCode: number) => {
console.log(`a client has disconnected: ${closeCode}`);
});
}
broadcast(data: Data) { // 全体广播
this.clientMap.forEach((client: any) => {
if (client.readyState === ws.OPEN) {
client.send(data);
}
});
}
}
export default WsRouter.getInstance;
// 使用:bootstrap.ts
const server = new InversifyExpressServer(container);
// 注:本项目后端使用的是 [Inversify](https://github.com/inversify) 框架
// 具体传的 private server?: hServer | hsServer 参数值,请类比改变
server.setConfig((app: any) => WsRouter('/ws/:id', app))
server.build().listen(4000);
复制代码
升级版本
要实现好友通信,在前后端的 send 方法中,当然要指定 from 和 to 的用户
再者,后台要记录发送的消息,也必须要有好友表的主键 friendId,表示为这两个人之间的消息
- mongoose 数据存储
// user.ts
const userSchema = new Schema(
{
name: { type: String, required: true, unique: true }
}
);
export default model('User', userSchema);
// friend.ts 两个用户之间的好友关系
import { Schema, model, Types } from 'mongoose';
const FriendSchema = new Schema(
{
user1: { type: Types.ObjectId, ref: 'User', required: true }, // user1Id < user2Id
user2: { type: Types.ObjectId, ref: 'User', required: true }
}
);
export default model('Friend', FriendSchema);
// message.ts
const MessageSchema = new Schema(
{
friend: { type: Types.ObjectId, ref: 'Friend', required: true }, // 关联 Friend 表
from: String,
to: String,
content: String,
type: { type: String, default: 'text' },
}
);
export default model('Message', MessageSchema);
复制代码
- 接口说明
type msgType = 'text' | 'emoji' | 'file'
interface IMessage {
from: string
to: string
content: string
type: msgType
}
复制代码
- 前端:WsRequest 封装类
import { IMessage, msgType } from './interface'
export default class WsRequest {
ws: WebSocket
constructor(url: string, private userId: string) {
this.ws = new WebSocket(`${url}/${this.userId}`)
this.initListeners()
}
initListeners() {
this.ws.onopen = _event => {
console.log('client connect')
}
this.ws.onmessage = event => {
const msg: IMessage = JSON.parse(event.data)
console.log(msg.content)
}
this.ws.onclose = _event => {
console.log('client disconnect')
}
}
// friendId 指 Friend Model 的 _id
async send(friendId: string, content: string, receiverId: string, type: msgType = 'text') {
const message: IMessage = { from: this.userId, to: receiverId, content, type }
await this.ws.send(JSON.stringify({ friend: friendId, ...message }))
}
close() {
this.ws.close()
}
}
// 使用
const ws = new WsRequest('your_websocket_url', 'your_user_id') // example: ws://localhost:4000/ws
await wsRequest.send('Friend_model_id', '你好啊,jeffery', 'jeffery_id')
复制代码
- 服务端:WsRouter 封装类,使用单例模式
import expressWs, { Application, Options } from 'express-ws';
import ws, { Data } from 'ws';
import { Server as hServer } from 'http';
import { Server as hsServer } from 'https';
import Message, { IMessage } from 'models/message';
import Friend from 'models/friend';
class WsRouter {
static instance: WsRouter;
wsServer: expressWs.Instance;
clientMap: Map<string, ws>; // 保存所有连接的用户 id
constructor(
private path: string,
private app: Application,
private server?: hServer | hsServer,
private options?: Options
) {
this.wsServer = expressWs(this.app, this.server, this.options);
this.app.ws(this.path, this.wsMiddleWare);
this.clientMap = new Map();
}
static getInstance(path: string, app: Application, server?: hServer | hsServer, options: Options = {}) {
if (!this.instance) {
this.instance = new WsRouter(path, app, server, options);
}
return this.instance;
}
wsMiddleWare = (wServer: any, req: any) => {
const { id } = req.params; // 解析用户 id
wServer.id = id;
this.clientMap.set(id, wServer);
wServer.on('message', async (data: Data) => {
const message: IMessage = JSON.parse(data.toString());
const { _id } = await new Message(message).save(); // 更新数据库
this.sendMsgToClientById(message);
});
wServer.on('close', (closeCode: number) => {
console.log(`a client has disconnected, closeCode: ${closeCode}`);
});
};
sendMsgToClientById(message: IMessage) {
const client: any = this.clientMap.get(message.to);
if (client) {
client!.send(JSON.stringify(message));
}
}
broadcast(data: Data) {
this.clientMap.forEach((client: any) => {
if (client.readyState === ws.OPEN) {
client.send(data);
}
});
}
}
export default WsRouter.getInstance;
// 使用:bootstrap.ts
const server = new InversifyExpressServer(container);
// 注:本项目后端使用的是 [Inversify](https://github.com/inversify) 框架
// 具体传的 private server?: hServer | hsServer 参数值,请类比改变
server.setConfig((app: any) => WsRouter('/ws/:id', app))
server.build().listen(4000);
复制代码
- 心跳检测
参考:
// 服务端
wsMiddleWare = (wServer: any, req: any) => {
const { id } = req.params;
wServer.id = id;
wServer.isAlive = true;
this.clientMap.set(id, wServer);
wServer.on('message', async (data: Data) => {...});
wServer.on('pong', () => {
wServer.isAlive = true;
});
}
initHeartbeat(during: number = 10000) {
return setInterval(() => {
this.clientMap.forEach((client: any) => {
if (!client.isAlive) {
this.clientMap.delete(client.id);
return client.terminate();
}
client.isAlive = false;
client.ping(() => {});
});
}, during);
}
复制代码
在线测试
一、准备
-
为方便大家测试,可以访问线上的服务端接口:
wss://qaapi.omyleon.com/ws,具体使用要附上用户 id,如:wss://qaapi.omyleon.com/ws/asdf...,参见 -
客户端:可以使用谷歌插件: Simple WebSocket Client ;也可以访问在线项目,使用项目提供的客户端,具体参见: qa-app
-
使用线上的一对好友信息
-
friend: jeffery 与 testuser => _id: 5d1328295793f14020a979d5
-
jeffery => _id: 5d1327c65793f14020a979ca
-
testuser => _id: 5d1328065793f14020a979cf
二、实际演示
- 打开 WebSocket Client 插件,输入测试链接,如下图:
wss://qaapi.omyleon.com/ws/5d1327c65793f14020a979ca 复制代码
- 点击
Open,下方Status显示Opened表示连接成功
- 发送消息,请根据接口来发送,当然还要附上
friendId,否则不能对应到相应的用户上
{
"friend": "5d1328295793f14020a979d5",
"from": "5d1327c65793f14020a979ca",
"to": "5d1328065793f14020a979cf",
"content": "你好呀,testuser,这是通过 simple websocket client 发送的消息",
"type": "text"
}
复制代码
- 同时用 simple websocket client 连接另一个用户,会收到发来的消息
- 同理,在另一个 client 中改变 from 和 to,就能回复消息给对方
{
"friend": "5d1328295793f14020a979d5",
"from": "5d1328065793f14020a979cf",
"to": "5d1327c65793f14020a979ca",
"content": "嗯嗯,收到了 jeffery,这也是通过 simple websocket client 发送的",
"type": "text"
}
复制代码
- 补充,在线上项目 qa-app 中,也是用的是个原理,只不过在显示时候做了解析,只显示了 content 字段,而在这个简易的测试客户端中没有做其他处理
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- itchat 保存好友信息以及生成好友头像图片墙
- 如何利用Python网络爬虫抓取微信好友数量以及微信好友的男女比例
- 微信好友大揭秘,使用Python抓取朋友圈数据,通过人脸识别全面分析好友,一起看透你的“朋友圈”
- geohash简单应用-面对面匹配好友
- 用 Pyecharts 可视化微信好友
- 原创 社交系统中用户好友关系数据库设计
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
啊哈C!思考快你一步
啊哈磊 / 电子工业出版社 / 2013-9 / 39.00元
这是一本非常有趣的编程启蒙书,全书从中小学生的角度来讲述,没有生涩的内容,取而代之的是生动活泼的漫画和风趣幽默的文字。并配合超萌的编程软件,从开始学习与计算机对话到自己独立制作一个游戏,由浅入深地讲述编程的思维。同时,与计算机展开的逻辑较量一定会让你觉得很有意思。你可以在茶余饭后阅读本书,甚至蹲在马桶上时也可以看得津津有味。编程将会改变我们的思维,教会我们如何思考,让我们的思维插上计算机的翅膀,以......一起来看看 《啊哈C!思考快你一步》 这本书的介绍吧!