客户端(浏览器)和服务器端进行通信,只能由客户端发起ajax请求,才能进行通信,服务器端无法主动向客户端推送信息。为了解决这一问题,websocket因此产生。
1、新建一个node服务器
var app = require('express')();
var server = require('http').Server(app);
var WebSocket = require('ws');
var wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
console.log('server: receive connection.');
ws.on('message', function incoming(message) {
console.log('server: received: %s', message);
});
setInterval(() => {
ws.send('world');
}, 1000)
});
app.listen(3000);
通过node命令启动node服务器
2、建立前端web
<script>
var ws = new WebSocket('ws://localhost:8080');
console.log(ws.readyState);
ws.onopen = function () {
console.log('ws onopen');
ws.send('from client: hello');
};
ws.onmessage = function (e) {
console.log('ws onmessage');
console.log('from server: ' + e.data);
};
ws.onclose = function (e) {
console.log("断开了");
}
ws.onerror = function () {
console.log("出错了");
}
</script>
websocket常用的四个api:
onopen :建立连接
onmessage :连接通信
onclose :连接关闭
onerror :连接出错
WebSocket的当前状态:WebSocket.readyState
下面是WebSocket.readyState的四个值(四种状态):
0: 表示正在连接
1: 表示连接成功,可以通信了
2: 表示连接正在关闭
3: 表示连接已经关闭,或者打开连接失败
我们可以利用当前状态来做一些事情,比如上面栗子中当WebSocket链接成功后,才允许客户端发送ping。
if (this.ws.readyState === 1) {
// 检查ws为链接状态 才可发送
this.ws.send('ping'); // 客户端发送ping
}
当项目中很多地方使用WebSocket,把它封成一个class类,是更好的选择。
下面的栗子,做了非常详细的注释,建个html文件也可直接使用,websocket的常用API都放进去了。
下方注释的代码,先不用管,涉及到心跳机制,用于保持WebSocket连接的
class WebSocketClass {
/**
* @description: 初始化实例属性,保存参数
* @param {String} url ws的接口
* @param {Function} msgCallback 服务器信息的回调传数据给函数
* @param {String} name 可选值 用于区分ws,用于debugger
*/
constructor(url, msgCallback, name = 'default') {
this.url = url;
this.msgCallback = msgCallback;
this.name = name;
this.ws = null; // websocket对象
this.status = null; // websocket是否关闭
}
/**
* @description: 初始化 连接websocket或重连webSocket时调用
* @param {*} 可选值 要传的数据
*/
connect(data) {
// 新建 WebSocket 实例
this.ws = new WebSocket(this.url);
this.ws.onopen = e => {
// 连接ws成功回调
this.status = 'open';
console.log(`${this.name}连接成功`, e)
// this.heartCheck();
if (data !== undefined) {
// 有要传的数据,就发给后端
return this.ws.send(data);
}
}
// 监听服务器端返回的信息
this.ws.onmessage = e => {
// 把数据传给回调函数,并执行回调
// if (e.data === 'pong') {
// this.pingPong = 'pong'; // 服务器端返回pong,修改pingPong的状态
// }
return this.msgCallback(e.data);
}
// ws关闭回调
this.ws.onclose = e => {
this.closeHandle(e); // 判断是否关闭
}
// ws出错回调
this.onerror = e => {
this.closeHandle(e); // 判断是否关闭
}
}
// heartCheck() {
// // 心跳机制的时间可以自己与后端约定
// this.pingPong = 'ping'; // ws的心跳机制状态值
// this.pingInterval = setInterval(() => {
// if (this.ws.readyState === 1) {
// // 检查ws为链接状态 才可发送
// this.ws.send('ping'); // 客户端发送ping
// }
// }, 10000)
// this.pongInterval = setInterval(() => {
// if (this.pingPong === 'ping') {
// this.closeHandle('pingPong没有改变为pong'); // 没有返回pong 重启webSocket
// }
// // 重置为ping 若下一次 ping 发送失败 或者pong返回失败(pingPong不会改成pong),将重启
// console.log('返回pong')
// this.pingPong = 'ping'
// }, 20000)
// }
// 发送信息给服务器
sendHandle(data) {
console.log(`${this.name}发送消息给服务器:`, data)
return this.ws.send(data);
}
closeHandle(e = 'err') {
// 因为webSocket并不稳定,规定只能手动关闭(调closeMyself方法),否则就重连
if (this.status !== 'close') {
console.log(`${this.name}断开,重连websocket`, e)
// if (this.pingInterval !== undefined && this.pongInterval !== undefined) {
// // 清除定时器
// clearInterval(this.pingInterval);
// clearInterval(this.pongInterval);
// }
this.connect(); // 重连
} else {
console.log(`${this.name}websocket手动关闭`)
}
}
// 手动关闭WebSocket
closeMyself() {
console.log(`关闭${this.name}`)
this.status = 'close';
return this.ws.close();
}
}
function someFn(data) {
console.log('接收服务器消息的回调:', data);
}
// const wsValue = new WebSocketClass('ws://121.40.165.18:8800', someFn, 'wsName'); // 这个链接一天只能发送消息50次
const wsValue = new WebSocketClass('wss://echo.websocket.org', someFn, 'wsName'); // 阮一峰老师教程链接
wsValue.connect('立即与服务器通信'); // 连接服务器
// setTimeout(() => {
// wsValue.sendHandle('传消息给服务器')
// }, 1000);
// setTimeout(() => {
// wsValue.closeMyself(); // 关闭ws
// }, 10000)
vue版
<template>
<div class="box">
websocket
</div>
</template>
<script>
const heartCheck = {
timeout: 60 * 1000,
timer: null,
serverTimer: null,
reset() {
this.timer && clearTimeout(this.timer)
this.serverTimer && clearTimeout(this.serverTimer)
},
start(ws) {
this.reset()
this.timer = setTimeout(() => {
// console.log('发送心跳,后端收到后,返回一个心跳消息')
// onmessage拿到返回的心跳就说明连接正常
ws.send(JSON.stringify({ heart: 1 }))
this.serverTimer = setTimeout(() => {
// 如果超过一定时间还没响应(响应后触发重置),说明后端断开了
ws.close()
}, this.timeout)
}, this.timeout)
}
}
export default {
name: 'Websocket',
data() {
return {
asrc,
wsuri: 'ws://123.207.167.163:9010/ajaxchattest', // ws wss
lockReconnect: false, // 连接失败不进行重连
maxReconnect: 5, // 最大重连次数,若连接失败
socket: null
}
},
mounted() {
this.initWebSocket()
},
methods: {
reconnect() {
console.log('尝试重连')
if (this.lockReconnect || this.maxReconnect <= 0) {
return
}
setTimeout(() => {
// this.maxReconnect-- // 不做限制 连不上一直重连
this.initWebSocket()
}, 60 * 1000)
},
initWebSocket() {
try {
if ('WebSocket' in window) {
this.socket = new WebSocket(this.wsuri)
} else {
console.log('您的浏览器不支持websocket')
}
this.socket.onopen = this.websocketonopen
this.socket.onerror = this.websocketonerror
this.socket.onmessage = this.websocketonmessage
this.socket.onclose = this.websocketclose
} catch (e) {
this.reconnect()
}
},
websocketonopen() {
console.log('WebSocket连接成功', this.socket.readyState)
heartCheck.start(this.socket)
// this.socket.send('发送数据')
this.websocketsend()
},
websocketonerror(e) {
console.log('WebSocket连接发生错误', e)
this.reconnect()
},
websocketonmessage(e) {
// console.log(e)
let data = JSON.parse(e.data)
console.log('得到响应', data)
console.log('可以渲染网页数据...')
// 消息获取成功,重置心跳
heartCheck.start(this.socket)
},
websocketclose(e) {
console.log('connection closed (' + e.code + ')')
this.reconnect()
},
websocketsend() {
let data = { id: 'a1b2c3' }
this.socket.send(JSON.stringify(data))
}
},
destroyed() {
this.socket.close()
}
}
</script>
<style lang="scss" scoped></style>