简介
很多网站的实时推送技术,所用的技术大多都是 Ajax 轮询。也就是隔几秒请求一次接口,显然这样会浪费很多的带宽等资源。WebSocket 允许服务端主动向客户端推送数据,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
特点
- 双向通信
- 没有同源策略(客户端可以与任意服务器通信)
- 二进制支持
工具
在线测试小工具
客户端实现
function WebSocketTest () {if ("WebSocket" in window) {console.log("您的浏览器支持 WebSocket!");var ws = new WebSocket("ws://localhost:9998/echo");ws.onopen = function () {ws.send("发送数据");alert("数据发送中...");};ws.onmessage = function (evt) {var received_msg = evt.data;alert("数据已接收...");};ws.onclose = function () {alert("连接已关闭...");};}else {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('Welcome Realtime Server
');
});
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');
});
<html lang&#61;"en">
<head><meta charset&#61;"UTF-8"><title>Documenttitle><script src&#61;"http://127.0.0.1:3000/socket.io/socket.io.js">script>
head>
<body><ul id&#61;"message">ul><script>socket &#61; io.connect(&#39;ws://127.0.0.1:3000&#39;);socket.emit("message", {"name" : navigator.userAgent, "msg" : "hello world"});socket.on("message", function(obj) {console.log(obj);});script>
body>
html>
简易的控制台版的聊天室
客户端心跳机制的实现
WebSocket并不稳定&#xff0c;在使用一段时间后&#xff0c;可能会断开连接&#xff0c;貌似至今没有一个为何会断开连接的公论&#xff0c;所以我们需要让WebSocket保持连接状态&#xff0c;这里推荐两种方法。
1、class类中就是用的这种方式:设置一个变量&#xff0c;在webSocket关闭/报错的回调中&#xff0c;判断是不是手动关闭的&#xff0c;如果不是的话&#xff0c;就重新连接&#xff0c;这样做的优缺点如下&#xff1a;
- 优点&#xff1a;请求较少(相对于心跳连接)&#xff0c;易设置。
- 缺点&#xff1a;可能会导致丢失数据,在断开重连的这段时间中&#xff0c;恰好双方正在通信。
2、客户端就像心跳一样每隔固定的时间发送一次ping&#xff0c;来告诉服务器&#xff0c;我还活着&#xff0c;而服务器也会返回pong&#xff0c;来告诉客户端&#xff0c;服务器还活着。具体方法再下文注释中。
class WebSocketClass {constructor(url, msgCallback, name &#61; &#39;default&#39;) {this.url &#61; url;this.msgCallback &#61; msgCallback;this.name &#61; name;this.ws &#61; null; this.status &#61; null; }connect(data) {this.ws &#61; new WebSocket(this.url);this.ws.onopen &#61; e &#61;> {this.status &#61; &#39;open&#39;;console.log(&#96;${this.name}连接成功&#96;, e)if (data !&#61;&#61; undefined) {return this.ws.send(data);}}this.ws.onmessage &#61; e &#61;> {return this.msgCallback(e.data);}this.ws.onclose &#61; e &#61;> {this.closeHandle(e); }this.onerror &#61; e &#61;> {this.closeHandle(e); }}sendHandle(data) {console.log(&#96;${this.name}发送消息给服务器:&#96;, data)return this.ws.send(data);}closeHandle(e &#61; &#39;err&#39;) {if (this.status !&#61;&#61; &#39;close&#39;) {console.log(&#96;${this.name}断开&#xff0c;重连websocket&#96;, e)this.connect(); } else {console.log(&#96;${this.name}websocket手动关闭&#96;)}}closeMyself() {console.log(&#96;关闭${this.name}&#96;)this.status &#61; &#39;close&#39;;return this.ws.close();}
}
function someFn(data) {console.log(&#39;接收服务器消息的回调&#xff1a;&#39;, data);
}
const wsValue &#61; new WebSocketClass(&#39;wss://echo.websocket.org&#39;, someFn, &#39;wsName&#39;);
wsValue.connect(&#39;立即与服务器通信&#39;);