在 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 服务器能提供给客户端的功能。

在 nodejs 中 利用 websocket 实现简单的 “1对1” 消息传递

客户端

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

相关推荐