前言
最近公司有项目接口要使用websocket的方式获取数据和通讯,倒腾了个简易封装,对于websocket的介绍及对比http的优劣势这里不做赘述可自行百度,废话不多说,上code,淦!(PS:附带可用测试的node服务端)。
一,客户端
1.定义websocket(后面简称ws),连接地址,onMessage回调,onError回调,均为对象格式,支持多个ws同时连接
let ws = {};
let urlObj = {};
let messageCallback = {};
let errorCallback = {};
2.定义接收ws后端返回的数据方法
function websocketOnMessage (key, e) {
messageCallback[key](JSON.parse(e.data));
}
3.定义ws关闭回调,删除对应的ws参数
function websocketOnClose (key, e) {
if (!ws[key]) return;
delete ws[key];
delete urlObj[key];
delete messageCallback[key];
delete errorCallback[key];
}
4.初始化ws连接
// 初始化ws
function initWebsocket (key) {
if (typeof (WebSocket) === 'undefined') {
console.error('您的浏览器不支持WebSocket,无法连接');
return;
}
// 需要组装url 可自行组装
// http: ws:xx, https: wss:xxx
ws[key] = new WebSocket(urlObj[key]);
ws[key].onopen = function () {
console.log(`${urlObj[key]} open-----`);
};
ws[key].onmessage = function (e) {
websocketOnMessage(key, e);
};
ws[key].onerror = function () {
console.error(`${urlObj[key]} 连接出错,请稍候重试`);
errorCallback[key]();
};
ws[key].onclose = function (e) {
console.log(`${urlObj[key]} close----`);
websocketOnClose(key, e);
};
}
5.ws初始连接成功时发送消息
/**
* @description:发起websocket连接
* @author Arezy
* @param {String} key
* @param {Object} params
* @returns
*/
function websocketConllection (key, params) {
if (!ws[key]) return;
if (ws[key].readyState === ws[key].OPEN) {
// OPEN状态发送消息给服务端
sendMessage(key, params);
} else if (ws[key].readyState === ws[key].CONNECTING) {
// 非OPEN状态,延长1秒(自行设置)重新调用
setTimeout(() => {
websocketConllection(key, params);
}, 1000);
}
if (ws[key].readyState === ws[key].CLOSED) {
console.error(`${urlObj[key]} 连接异常,请稍候重试`);
errorCallback[key]();
}
}
6.创建ws连接,以url为key 为每个ws创建对应的url,onMessage回调,onError回调
/**
* @description: 添加ws连接对象
* @author Arezy
* @export
* @param {Object}
* {
* url, 连接url
* params = {}, 约定参数
* onMessage, 获取服务消息回调
* onError: 出错回调
* }
*/
export function sendWebsocket ({ url, params = {}, onMessage, onError }) {
urlObj[url] = url;
messageCallback[url] = onMessage;
errorCallback[url] = onError;
initWebsocket(url);
websocketConllection(url, params);
}
7.定义发送消息的方法,导出共外部调用
/**
* @description: 发送消息
* @author Arezy
* @export
* @param {String} key
* @param {Object} data 和服务约定参数格式
*/
export function sendMessage (key, data) {
// 发给后端的数据需要字符串化
ws[key].send(JSON.stringify(data));
}
8.定义关闭对应ws连接方法,供外部调用
/**
* @description: 关闭ws连接
* @author Arezy
* @export
*/
export function closeWebsocket (key) {
if (ws[key]) {
ws[key].close();
}
}
9.完整版js
let ws = {};
let urlObj = {};
let messageCallback = {};
let errorCallback = {};
// 接收ws后端返回的数据
function websocketOnMessage (key, e) {
messageCallback[key](JSON.parse(e.data));
}
function websocketOnClose (key, e) {
if (!ws[key]) return;
delete ws[key];
delete urlObj[key];
delete messageCallback[key];
delete errorCallback[key];
}
/**
* @description:发起websocket连接
* @author Arezy
* @param {String} key
* @param {Object} params
* @returns
*/
function websocketConllection (key, params) {
if (!ws[key]) return;
if (ws[key].readyState === ws[key].OPEN) {
// OPEN状态发送消息给服务端
sendMessage(key, params);
} else if (ws[key].readyState === ws[key].CONNECTING) {
// 非OPEN状态,延长1秒(自行设置)重新调用
setTimeout(() => {
websocketConllection(key, params);
}, 1000);
}
if (ws[key].readyState === ws[key].CLOSED) {
console.error(`${urlObj[key]} 连接异常,请稍候重试`);
errorCallback[key]();
}
}
// 初始化ws
function initWebsocket (key) {
if (typeof (WebSocket) === 'undefined') {
console.error('您的浏览器不支持WebSocket,无法连接');
return;
}
// 需要组装url 可自行组装
// http: ws:xx, https: wss:xxx
ws[key] = new WebSocket(urlObj[key]);
ws[key].onopen = function () {
console.log(`${urlObj[key]} open-----`);
};
ws[key].onmessage = function (e) {
websocketOnMessage(key, e);
};
ws[key].onerror = function () {
console.error(`${urlObj[key]} 连接出错,请稍候重试`);
errorCallback[key]();
};
ws[key].onclose = function (e) {
console.log(`${urlObj[key]} close----`);
websocketOnClose(key, e);
};
}
/**
* @description: 添加ws连接对象
* @author Arezy
* @export
* @param {Object}
* {
* url, 连接url
* params = {}, 约定参数
* onMessage, 获取服务消息回调
* onError: 出错回调
* }
*/
export function sendWebsocket ({ url, params = {}, onMessage, onError }) {
urlObj[url] = url;
messageCallback[url] = onMessage;
errorCallback[url] = onError;
initWebsocket(url);
websocketConllection(url, params);
}
/**
* @description: 发送消息
* @author Arezy
* @export
* @param {String} key
* @param {Object} data 和服务约定参数格式
*/
export function sendMessage (key, data) {
// 发给后端的数据需要字符串化
ws[key].send(JSON.stringify(data));
}
/**
* @description: 关闭ws连接
* @author Arezy
* @export
*/
export function closeWebsocket (key) {
if (ws[key]) {
ws[key].close();
}
}
10,在Vue中调用(可在其他任意框架中使用),
调用比较简单,通过sendWebsocket 传入url,参数params,onMessage回调(获取服务数据,笔者这里直接打印出来了), onError回调(处理异常),通过sendMessage 持续通讯,closeWebsocket关闭ws连接(beforeDestroy钩子函数调用,关闭当前组件所有连接),也可手动关闭
<!--
* @Description: 测试webSocket
* @Author: Arezy
* @Date: 2021-01-19 19:45:51
-->
<template>
<div class="web-socket">
<div class="btn"
@click="sendMsg">发送消息toUser</div>
<div class="btn"
@click="closeLogin">关闭userLogin</div>
<div class="btn"
@click="talking">连接其他路由</div>
<div class="btn"
@click="sendTacking">发送消息到talking</div>
</div>
</template>
<script>
import { sendWebsocket, closeWebsocket, sendMessage } from '../../utils/socket.service.js';
export default {
'name': 'Websocket',
data () {
return {
url: 'ws:localhost:3000/user/login?token=123456789',
talkingUrl: 'ws:localhost:3000/user/talking/usid001'
};
},
created () {
this.init();
},
methods: {
// 默认初始第一个ws连接
init () {
sendWebsocket({
url: this.url,
params: { name: 'lily' },
onMessage: this.message,
onError: this.onError
});
},
message (data) {
console.log('login ws message: ', data);
},
onError () {
console.log('login ws error');
},
closeLogin () {
closeWebsocket(this.url);
},
sendMsg () {
sendMessage(this.url, { name: 'Tony' });
},
// 创建第二个ws连接
talking () {
sendWebsocket({
url: this.talkingUrl,
params: { name: '西西', context: '你好啊,切图仔' },
onMessage: this.cMessage,
onError: this.conError
});
},
cMessage (data) {
console.log('talking ws message: ', data);
},
conError () {
console.log('talking ws error');
},
sendTacking () {
sendMessage(this.talkingUrl, { name: '西西', context: '淦,我不切图了' });
}
},
beforeDestroy () {
closeWebsocket(this.url);
closeWebsocket(this.talkingUrl);
}
};
</script>
<style lang="less" scoped>
.web-socket {
position: relative;
width: 100%;
margin-left: 50px;
.btn {
display: flex;
justify-content: center;
align-items: center;
width: 150px;
padding: 5px 15px;
background: #2f7dcd;
color: #ffffff;
border-radius: 5px;
margin-top: 25px;
}
}
</style>
二,服务端(node.js)
服务端很简洁,只用来测试ws连接;
以express + express-ws构建
nodemon检测修改自动重启服务
1.index.js 使用express express-ws启动服务
var express = require('express');
var expressWs = require('express-ws');
var userRouter = require('./router/user.router.js');
var app = express();
expressWs(app);
app.use('/user', userRouter);
app.set('port', process.env.PORT || 3000); // 设定监听端口
var server = app.listen(app.get('port'), function () {
console.log('Express server listening on port ' + server.address().port);
});
2.user.router.js 支持ws连接 和 http连接
var express = require('express');
var expressWs = require('express-ws');
var router = express.Router();
expressWs(router);
router
.ws('/login', function (ws, req) {
ws.on('message', function (msg) {
// 业务代码
const name = JSON.parse(msg).name;
const resp = {
token: req.query.token,
name: name,
message: `${name} login`
};
console.log('login msg req: ', resp);
ws.send(JSON.stringify(resp));
});
})
.get('/login', function (req, res) {
console.log("🚀 ~ file: user.router.js ~ line 21 ~ req", req.query)
res.send(req.query);
})
.post('/login', function (req, res) {
});
router
.ws('/talking/:usessionId', function (ws, req) {
console.log("🚀 ~ file: user.router.js ~ line 34 ~ req", req.query)
console.log("🚀 ~ file: user.router.js ~ line 34 ~ req", req.params);
ws.on('message', function (msg) {
// 业务代码
const { context = '', name = '' } = JSON.parse(msg);
const resp = {
name: name,
message: `${name} talking ${context}`
};
console.log('talking msg req: ', resp);
ws.send(JSON.stringify(resp));
});
})
module.exports = router;
node仓库地址(gitee):https://gitee.com/ArezyLuo/node--ws-test
clone仓库 npm i
装包 npm start
即可运行服务
总结
node服务跑起来,即可测试ws,希望可以帮到需要的小伙伴~