node系列:初探websocket

简介

很多网站的实时推送技术,所用的技术大多都是 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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lvan的前端笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值