成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

websocket 實(shí)戰(zhàn)——好友聊天

LMou / 2667人閱讀

摘要:實(shí)戰(zhàn)好友聊天原文地址實(shí)戰(zhàn)好友聊天還不了解的同鞋,請(qǐng)先學(xué)習(xí)阮一峰老師的教程在實(shí)際項(xiàng)目中有著很廣的應(yīng)用,如好友聊天,異步請(qǐng)求,的熱更新等等本文前端采用原生,后端采用庫(kù)實(shí)現(xiàn)聊天通信后端數(shù)據(jù)存儲(chǔ)采用操作,不了解的可以先看看文檔哦聊天原理很簡(jiǎn)單

JS websocket 實(shí)戰(zhàn)——好友聊天

原文地址:websocket 實(shí)戰(zhàn)——好友聊天

還不了解 websocket 的同鞋,請(qǐng)先學(xué)習(xí)阮一峰老師的 WebSocket 教程

websocket

websocket 在實(shí)際項(xiàng)目中有著很廣的應(yīng)用,如好友聊天,異步請(qǐng)求,react-hot-loader 的熱更新等等

本文前端采用原生 WebSocket,后端采用 express-ws 庫(kù) 實(shí)現(xiàn)聊天通信

后端 mongodb 數(shù)據(jù)存儲(chǔ)采用 mongoose 操作,不了解的可以先看看 文檔 哦

聊天原理很簡(jiǎn)單,如下圖:

簡(jiǎn)單版本

先擼個(gè)簡(jiǎn)單版本,能夠?qū)崿F(xiàn)用戶與服務(wù)器之間的通信

前端:WsRequest 封裝類(lèi)

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(`來(lái)自服務(wù)器的信息:${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")

服務(wù)端:WsRouter 封裝類(lèi),使用單例模式

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(`來(lái)自用戶的信息:${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);
// 注:本項(xiàng)目后端使用的是 [Inversify](https://github.com/inversify) 框架
// 具體傳的 private server?: hServer | hsServer 參數(shù)值,請(qǐng)類(lèi)比改變
server.setConfig((app: any) => WsRouter("/ws/:id", app))
server.build().listen(4000);
升級(jí)版本

要實(shí)現(xiàn)好友通信,在前后端的 send 方法中,當(dāng)然要指定 fromto 的用戶

再者,后臺(tái)要記錄發(fā)送的消息,也必須要有好友表的主鍵 friendId,表示為這兩個(gè)人之間的消息

mongoose 數(shù)據(jù)存儲(chǔ)

// user.ts
const userSchema = new Schema(
  {
    name: { type: String, required: true, unique: true }
  }
);

export default model("User", userSchema);

// friend.ts 兩個(gè)用戶之間的好友關(guān)系
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 }, // 關(guān)聯(lián) Friend 表
    from: String,
    to: String,
    content: String,
    type: { type: String, default: "text" },
  }
);

export default model("Message", MessageSchema);

接口說(shuō)明

type msgType = "text" | "emoji" | "file"

interface IMessage {
  from: string
  to: string
  content: string
  type: msgType
}

前端:WsRequest 封裝類(lèi)

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")

服務(wù)端:WsRouter 封裝類(lèi),使用單例模式

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(); // 更新數(shù)據(jù)庫(kù)

      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);
// 注:本項(xiàng)目后端使用的是 [Inversify](https://github.com/inversify) 框架
// 具體傳的 private server?: hServer | hsServer 參數(shù)值,請(qǐng)類(lèi)比改變
server.setConfig((app: any) => WsRouter("/ws/:id", app))
server.build().listen(4000);

心跳檢測(cè)

參考:

ws faq: how-to-detect-and-close-broken-connections

// 服務(wù)端
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);
}
在線測(cè)試 一、準(zhǔn)備

為方便大家測(cè)試,可以訪問(wèn)線上的服務(wù)端接口: wss://qaapi.omyleon.com/ws,具體使用要附上用戶 id,如:wss://qaapi.omyleon.com/ws/asdf...,參見(jiàn) 升級(jí)版本的 websocket

客戶端:可以使用谷歌插件:Simple WebSocket Client;也可以訪問(wèn)在線項(xiàng)目,使用項(xiàng)目提供的客戶端,具體參見(jiàn):qa-app

使用線上的一對(duì)好友信息

friend: jeffery 與 testuser => _id: 5d1328295793f14020a979d5

jeffery => _id: 5d1327c65793f14020a979ca

testuser => _id: 5d1328065793f14020a979cf

二、實(shí)際演示

打開(kāi) WebSocket Client 插件,輸入測(cè)試鏈接,如下圖:

wss://qaapi.omyleon.com/ws/5d1327c65793f14020a979ca

點(diǎn)擊 Open,下方 Status 顯示 Opened 表示連接成功

發(fā)送消息,請(qǐng)根據(jù) IMessage 接口來(lái)發(fā)送,當(dāng)然還要附上 friendId,否則不能對(duì)應(yīng)到相應(yīng)的好友關(guān)系上

{
  "friend": "5d1328295793f14020a979d5",
  "from": "5d1327c65793f14020a979ca",
  "to": "5d1328065793f14020a979cf",
  "content": "你好呀,testuser,這是通過(guò) simple websocket client 發(fā)送的消息",
  "type": "text"
}

同時(shí)用 simple websocket client 連接另一個(gè)用戶,會(huì)收到發(fā)來(lái)的消息

同理,在另一個(gè) client 中改變 from 和 to,就能回復(fù)消息給對(duì)方

wss://qaapi.omyleon.com/ws/5d1328065793f14020a979cf

{
  "friend": "5d1328295793f14020a979d5",
  "from": "5d1328065793f14020a979cf",
  "to": "5d1327c65793f14020a979ca",
  "content": "嗯嗯,收到了 jeffery,這也是通過(guò) simple websocket client 發(fā)送的",
  "type": "text"
}

補(bǔ)充,在線上項(xiàng)目 qa-app 中,也是用的是個(gè)原理,只不過(guò)在顯示時(shí)候做了解析,只顯示了 content 字段,而在這個(gè)簡(jiǎn)易的測(cè)試客戶端中沒(méi)有做其他處理

源碼獲取

前端:qa-app app/websocket/index.ts

后端:qa-app-server server/wsRouter/index.ts

線上地址,去看看 -> https://qa.omyleon.com

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/105159.html

相關(guān)文章

  • Spring整合Netty、WebSocket的互聯(lián)網(wǎng)聊天系統(tǒng)

    摘要:當(dāng)用戶注銷(xiāo)或退出時(shí),釋放連接,清空對(duì)象中的登錄狀態(tài)。聊天管理模塊系統(tǒng)的核心模塊,這部分主要使用框架實(shí)現(xiàn),功能包括信息文件的單條和多條發(fā)送,也支持表情發(fā)送。描述讀取完連接的消息后,對(duì)消息進(jìn)行處理。 0.前言 最近一段時(shí)間在學(xué)習(xí)Netty網(wǎng)絡(luò)框架,又趁著計(jì)算機(jī)網(wǎng)絡(luò)的課程設(shè)計(jì),決定以Netty為核心,以WebSocket為應(yīng)用層通信協(xié)議做一個(gè)互聯(lián)網(wǎng)聊天系統(tǒng),整體而言就像微信網(wǎng)頁(yè)版一樣,但考慮...

    My_Oh_My 評(píng)論0 收藏0
  • 基于VUE多人聊天項(xiàng)目

    摘要:項(xiàng)目背景公司平臺(tái)要做一個(gè)通訊系統(tǒng),本來(lái)是來(lái)做的后面改前端來(lái)做,所以就用來(lái)做這個(gè)了。 項(xiàng)目背景 公司平臺(tái)要做一個(gè)通訊系統(tǒng),本來(lái)是java 來(lái)做的后面改前端+PHP來(lái)做,所以就用VUE來(lái)做這個(gè)了。 github github地址 新人求star 技術(shù)棧 vue-axios vuex websocket sass css3 等... 已經(jīng)完成進(jìn)度 整體結(jié)構(gòu)已經(jīng)完成 ...

    0xE7A38A 評(píng)論0 收藏0
  • 基于VUE多人聊天項(xiàng)目

    摘要:項(xiàng)目背景公司平臺(tái)要做一個(gè)通訊系統(tǒng),本來(lái)是來(lái)做的后面改前端來(lái)做,所以就用來(lái)做這個(gè)了。 項(xiàng)目背景 公司平臺(tái)要做一個(gè)通訊系統(tǒng),本來(lái)是java 來(lái)做的后面改前端+PHP來(lái)做,所以就用VUE來(lái)做這個(gè)了。 github github地址 新人求star 技術(shù)棧 vue-axios vuex websocket sass css3 等... 已經(jīng)完成進(jìn)度 整體結(jié)構(gòu)已經(jīng)完成 ...

    joywek 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

LMou

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<