简介
很多网站的实时推送技术,所用的技术大多都是 Ajax 轮询。也就是隔几秒请求一次接口,显然这样会浪费很多的带宽等资源。WebSocket 允许服务端主动向客户端推送数据,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
特点
- 双向通信
- 没有同源策略(客户端可以与任意服务器通信)
- 二进制支持
工具
客户端实现
function WebSocketTest () {
if ("WebSocket" in window) {
console.log("您的浏览器支持 WebSocket!");
// 打开一个 web socket
var ws = new WebSocket("ws://localhost:9998/echo");
ws.onopen = function () {
// Web Socket 已连接上,使用 send() 方法发送数据
ws.send("发送数据");
alert("数据发送中...");
};
ws.onmessage = function (evt) {
var received_msg = evt.data;
alert("数据已接收...");
};
ws.onclose = function () {
// 关闭 websocket
alert("连接已关闭...");
};
}
else {
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!");
}
}
后台实现(node)
由于原生的node实现起来非常复杂,需要牵扯到一些底层的如数据帧的编码解码,所以我们这里直接用已经实现好的第三方库,比如socket.io,WebSocket-Node
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function (req, res) {
res.send('<h1>Welcome Realtime Server</h1>');
});
io.on('connection', function (socket) {
console.log('a user connected');
socket.on("disconnect", function () {
console.log("a user go out");
});
socket.on("message", function (obj) {
io.emit("message", obj);
});
});
http.listen(3000, function () {
console.log('listening on *:3000');
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="http://127.0.0.1:3000/socket.io/socket.io.js"></script>
</head>
<body>
<ul id="message"></ul>
<script>
socket = io.connect('ws://127.0.0.1:3000');
socket.emit("message", {"name" : navigator.userAgent, "msg" : "hello world"});
socket.on("message", function(obj) {
console.log(obj);
});
</script>
</body>
</html>
简易的控制台版的聊天室
客户端心跳机制的实现
WebSocket并不稳定,在使用一段时间后,可能会断开连接,貌似至今没有一个为何会断开连接的公论,所以我们需要让WebSocket保持连接状态,这里推荐两种方法。
1、class类中就是用的这种方式:设置一个变量,在webSocket关闭/报错的回调中,判断是不是手动关闭的,如果不是的话,就重新连接,这样做的优缺点如下:
- 优点:请求较少(相对于心跳连接),易设置。
- 缺点:可能会导致丢失数据,在断开重连的这段时间中,恰好双方正在通信。
2、客户端就像心跳一样每隔固定的时间发送一次ping,来告诉服务器,我还活着,而服务器也会返回pong,来告诉客户端,服务器还活着。具体方法再下文注释中。
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(() => {
// this.pingPong = false;
// 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)