在 nodejs 中 利用 websocket 實現簡單的 “1對1” 消息傳遞

背景

簡單的描述一下需求場景:應用需要進行客戶端到客戶端的通信,websocket 就能很好的進行這一操作,目前 網易雲信的 IM 等功能也是利用 websocket 進行的。

必要性

對前端開發人員來說,目前能夠提供 mock 服務的第三方工具還是比較多的,基本上,與後臺開發人員約定好請求路徑、請求字段和響應字段之後就能前後臺獨立開發了。

但 websocket 服務器與 http 服務器最大的區別就是 websocket 服務器必須得一直提供服務,否則客戶端之間就無法進行通信。

爲了體現前後端分離,提高開發效率的精髓。肯定是不能先把邏輯全部盲寫好了之後再與後臺聯調的,便決定與後臺約定好了接口名和數據形式之後,用 nodejs 寫一個簡單的 websocket 通信服務。

功能

websocket 用的比較多的應該就是傳輸文本(簡單點說就是字符串),所以,這個字符串攜帶着接收方的用戶標識(toId),其他的信息(比如消息類型 type 和 消息內容 data 等),通常的做法是 JSON.stringfy() 之後轉成字符串,服務端將發送方的用戶標識(fromId)、其他的信息(比如發送過來的消息類型 type 和 消息內容 data)等信息轉發給接收方,接收方接收到字符串之後再 JSON.parse() 進行下一步操作。

用下面這張圖描述一下我希望 websocket 服務器能提供給客戶端的功能。

圖片描述

客戶端

websocket 提供的方法中常用的有:newonopenoncloseonerroronmessagesend

毫無疑問,對前端開發人員來說需要與業務邏輯相結合、重點關注的方法就是以下三個:

new

在連接 websocket 服務器時傳遞自己的 id

const ws = new WebSocket(`ws://localhost:8080/websocketServer/${id}`);

send

發送消息(字符串)給服務器,服務器再轉發給目標

const msgObj = {
    toId: 666,
    type: 'hello',
    data: 'message......'
};
const msgStr = JSON.stringfy(msgObj);
ws.send(msgStr);

sendTo

由於 send 不能接收對象或者數組類型的數據,每次都得寫一個臨時的對象,再調用 JSON.stringfy() 方法轉成字符串。

那就封裝一個符合自己業務場景的方法,簡化開發過程中實現發送功能的步驟。

// 在原型對象上增加的 sendTo 方法
if ( ! WebSocket.prototype.sendTo ) {
    WebSocket.prototype.sendTo = function(toId, type, data) {
        const msg = JSON.stringify({ toId, type, data });
        this.send(msg);
    }
}

// 調用形式
ws.sendTo(toId, type, data);

onmessage

接收消息,通常會根據消息中 type 的不同值:

  • 自己做點什麼
  • 給對方一個響應(調用 sendTo 方法)
ws.onmessage = function(msg) {
    const { fromId, type, data } = JSON.parse(msg);
    switch (type) {
        case 'message':
            // 自己做點什麼
            break;
        
        case 'hello':
            // 返回一個消息
            this.sendTo(fromId, 'response', 'response data');
    }
}

最後簡單的封裝一下初始化方法,方便在不同的地方使用。

const wsUrl = 'ws://localhost:8080/';
const wsPath = 'socketServer/';
// 生成實例
let ws = null;
const connect = async () => {
    ws = await connectWS(uid, messageHandle);
    // ...
    // ...
    // 連接成功之後做點什麼
}

function connectWS(uid, msgHandle) {
    if ( ! WebSocket.prototype.sendTo ) {
        WebSocket.prototype.sendTo = function(toId, type, data) {
            const msg = JSON.stringify({ toId, type, data });
            this.send(msg);
        }
    }
    
    return new Promise((resolve, reject) => {
        const ws = new WebSocket(`${wsUrl}${wsPath}${uid}`);
        ws.onopen = () => {
            console.log('open');
            resolve(ws);
        };
        ws.onclose = () => {
            console.log('close');
        };
        ws.onerror = () => {
            console.log('error');
        };
        ws.onmessage = (msg) => {
            msgHandle(JSON.parse(msg.data));
        };
    });
}
// 接收消息的處理函數
function messageHandle(msgData) {
    const { fromId, type, data } = msgData;
    switch (type) {
        case '':
            break;
        default:
            break;
    }
}

服務端

常用的 nodejs-websocketexpress-ws 兩個框架,都需要自身維護一個對象來記錄每個 websocket 連接是哪個用戶,從而實現消息轉發。

express-ws 爲例:

記錄連接

首先,服務端要記錄當前連接的用戶,以便轉發消息的時候能夠正確發送給目標。

轉發消息

服務器將收到的來自於發送方消息中的 toId 值作爲要轉發的目標(接收方),在服務器自身維護的對象中找到接收方的這個連接,然後將發送方的標識作爲 fromId 轉發給接收方。

const express = require('express');
const express_ws = require('express-ws');
const app = express();
const wsObj = {};
express_ws(app);

app.ws('/socketServer/:uid', (ws, req) => {
    const uid = req.params.uid;
    wsObj[uid] = ws;
    ws.onmessage = (msg) => {
        let { toId, type, data} = JSON.parse(msg.data);
        const fromId = uid;
        if (fromId != toId && wsObj[toId]) {
            // wsObj[toId]   表示 接收方 與服務器的那條連接
            // wsObj[fromId] 表示 發送方 與服務器的那條連接
            wsObj[toId].send(JSON.stringify( { fromId, type, data } ))
        }
    }
});

app.listen(8080);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章