摘要:實戰好友聊天原文地址實戰好友聊天還不了解的同鞋,請先學習阮一峰老師的教程在實際項目中有著很廣的應用,如好友聊天,異步請求,的熱更新等等本文前端采用原生,后端采用庫實現聊天通信后端數據存儲采用操作,不了解的可以先看看文檔哦聊天原理很簡單
JS websocket 實戰——好友聊天
原文地址:websocket 實戰——好友聊天
還不了解 websocket 的同鞋,請先學習阮一峰老師的 WebSocket 教程
websocketwebsocket 在實際項目中有著很廣的應用,如好友聊天,異步請求,react-hot-loader 的熱更新等等
本文前端采用原生 WebSocket,后端采用 express-ws 庫 實現聊天通信
后端 mongodb 數據存儲采用 mongoose 操作,不了解的可以先看看 文檔 哦
聊天原理很簡單,如下圖:
簡單版本先擼個簡單版本,能夠實現用戶與服務器之間的通信
前端: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升級版本; // 保存所有連接的用戶 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; // 保存所有連接的用戶 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);
心跳檢測
參考:
ws faq: how-to-detect-and-close-broken-connections
// 服務端 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...,參見 升級版本的 websocket
客戶端:可以使用谷歌插件: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 表示連接成功
發送消息,請根據 IMessage 接口來發送,當然還要附上 friendId,否則不能對應到相應的好友關系上
{ "friend": "5d1328295793f14020a979d5", "from": "5d1327c65793f14020a979ca", "to": "5d1328065793f14020a979cf", "content": "你好呀,testuser,這是通過 simple websocket client 發送的消息", "type": "text" }
同時用 simple websocket client 連接另一個用戶,會收到發來的消息
同理,在另一個 client 中改變 from 和 to,就能回復消息給對方
wss://qaapi.omyleon.com/ws/5d1328065793f14020a979cf { "friend": "5d1328295793f14020a979d5", "from": "5d1328065793f14020a979cf", "to": "5d1327c65793f14020a979ca", "content": "嗯嗯,收到了 jeffery,這也是通過 simple websocket client 發送的", "type": "text" }
補充,在線上項目 qa-app 中,也是用的是個原理,只不過在顯示時候做了解析,只顯示了 content 字段,而在這個簡易的測試客戶端中沒有做其他處理
源碼獲取前端:qa-app app/websocket/index.ts
后端:qa-app-server server/wsRouter/index.ts
線上地址,去看看 -> https://qa.omyleon.com
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105159.html
摘要:當用戶注銷或退出時,釋放連接,清空對象中的登錄狀態。聊天管理模塊系統的核心模塊,這部分主要使用框架實現,功能包括信息文件的單條和多條發送,也支持表情發送。描述讀取完連接的消息后,對消息進行處理。 0.前言 最近一段時間在學習Netty網絡框架,又趁著計算機網絡的課程設計,決定以Netty為核心,以WebSocket為應用層通信協議做一個互聯網聊天系統,整體而言就像微信網頁版一樣,但考慮...
摘要:項目背景公司平臺要做一個通訊系統,本來是來做的后面改前端來做,所以就用來做這個了。 項目背景 公司平臺要做一個通訊系統,本來是java 來做的后面改前端+PHP來做,所以就用VUE來做這個了。 github github地址 新人求star 技術棧 vue-axios vuex websocket sass css3 等... 已經完成進度 整體結構已經完成 ...
摘要:項目背景公司平臺要做一個通訊系統,本來是來做的后面改前端來做,所以就用來做這個了。 項目背景 公司平臺要做一個通訊系統,本來是java 來做的后面改前端+PHP來做,所以就用VUE來做這個了。 github github地址 新人求star 技術棧 vue-axios vuex websocket sass css3 等... 已經完成進度 整體結構已經完成 ...
閱讀 825·2019-08-30 15:55
閱讀 1406·2019-08-30 13:55
閱讀 1983·2019-08-29 17:13
閱讀 2840·2019-08-29 15:42
閱讀 1331·2019-08-26 14:04
閱讀 1016·2019-08-26 13:31
閱讀 3271·2019-08-26 11:34
閱讀 828·2019-08-23 18:25