轮询、SSE和webSocket

1,312 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情

轮询(Polling)是一种CPU决策如何提供周边设备服务的方式,又称“程控输入输出”(Programmed I/O)。轮询法的概念是:由CPU定时发出询问,依序询问每一个周边设备是否需要其服务,有即给予服务,服务结束后再问下一个周边,接着不断周而复始。

短轮询

短轮询的基本思路:

  • 浏览器每隔一段时间向浏览器发送 http 请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。
  • 这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。

具体操作:可以通过定时器/延时器发起http请求

// 定时器
setInterval(function() {
    axios.request({
        url: '/get_vote',
        method: 'get'
    }).then(function (response) {
       //do something
    })
}, 10000);

优缺点👇

  • 优点是比较简单,易于理解。
  • 缺点是这种方式由于需要不断的建立 http 连接,严重浪费了服务器端和客户端的资源。当用户增加时,服务器端的压力就会变大,这是很不合理的。

长轮询

长轮询的基本思路:

  • 首先由客户端向服务器发起请求,当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。
  • 如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制才返回。客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。

具体实现: 后端写sleep(秒) 睡眠挂起请求,就是把前端的定时器移动到了后端, 后端while循环,不停的问数据库有没有结果。 没有进入定时睡眠,有则跳出循环处理逻辑。

// 获取最新的投票结果
function getData() {
    axios.request({
         method: "get",
         url: '接口地址',
     }).then(function (data) {
       //do something
        if (response.data != '') {
            // 获取到最新的数据do somethings
        }
        // 获取完数据后,再发送请求,看还有没有新数据生成
        getData()
    })
}

优缺点👇

  • 长轮询和短轮询比起来,它的优点是明显减少了很多不必要的 http 请求次数,相比之下节约了资源。
  • 长轮询的缺点在于,连接挂起也会导致资源的浪费(服务器压力大,频繁操作询问数据库有没有新结果)

webSocket

由于 http 存在一个明显的弊端(消息只能有客户端推送到服务器端,而服务器端不能主动推送到客户端),导致如果服务器如果有连续的变化,这时只能使用轮询,而轮询效率过低,并不适合。于是 WebSocket被发明出来

WebSocket 是 Html5 定义的一个新协议,与传统的 http 协议不同,Websocket 是一个持久化的协议,该协议允许由服务器主动的向客户端推送信息。

使用 WebSocket 协议的缺点是在服务器端的配置比较复杂。WebSocket 是一个全双工的协议,也就是通信双方是平等的,可以相互发送消息。

websocket特点

  • 支持双向通信,实时性更强;
  • 可以发送文本,也可以二进制文件;
  • 协议标识符是 ws,加密后是 wss
  • 较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP协议每次通信都需要携带完整的头部;
  • 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
  • 无跨域问题。

websocket 最大的特点就是可以双向通信。 通信双方都可以主动发送信息。

实现比较简单,服务端库如 socket.iows,可以很好的帮助我们入门。而客户端也只需要参照 api 实现即可:阮一峰websocket

WebSocket的用法示例:

    //创建WebSocket的对象。参数可以是 ws 或 wss,后者表示加密。
    var ws = new WebSocket('wss://echo.websocket.org');
    //发送请求
    ws.onopen = function (evt) {
        console.log('Connection open ...');
        ws.send('Hello WebSockets!');
    };
    //接收数据
    ws.onmessage = function (evt) {
        console.log('Received Message: ', evt.data);
        ws.close();
    };
    //关闭连接
    ws.onclose = function (evt) {
        console.log('Connection closed.');
    };

适用/兼容场景:

  • FLASH Socket
  • 长轮询: 定时发送 ajax
  • long poll: 发送 --> 有消息时再 response

常用api

  • new WebSocket(url)
  • ws.onerror = fn
  • ws.onclose = fn
  • ws.onopen = fn
  • ws.onmessage = fn
  • ws.send()

长连接SSE

SSE是HTML5新增的功能,SSE(sever-sent events)服务器端推送事件,是指服务器推送数据给客户端,而不是传统的请求响应模式。

EventSource 是服务器推送的一个网络事件接口。一个EventSource实例会对HTTP服务开启一个持久化的连接,以text/event-stream 格式发送事件, 会一直保持开启直到被要求关闭。

与WebSocket不同的是,SSE是服务端单向推送数据到客户端。数据信息被单向从服务端到客户端分发,当不需要以消息形式将数据从客户端发送到服务器时,这使它们成为绝佳的选择。例如,对于处理社交媒体状态更新,新闻提要或将数据传递到客户端存储机制(如IndexedDB或Web存储)之类的,EventSource无疑是一个有效方案。

注:IE不支持

// 前端接收数据信息
// 加上兼容判断
if(typeof(EventSource)!=="undefined"){
    var source = new EventSource('/test/xx');  //指定路由发送
    source.onopen = function(e) { 
      //当连接正式建立时触发
      console.log(e);
    };
    source.onmessage = function(e) {  
        //监听信息的传输
        var data = JSON.parse(e.data),
        origin = e.origin;
        console.log(data);
        if (!data) {
            // 数据传输完毕,无数据时关闭连接
            source.close()
        }
       //data   服务器端传回的数据
       //origin服务器端URL的域名部分,有protocol,hostname,port
       //lastEventId用来指定当前数据的序号.主要用来断线重连时数据的有效性
    };
    source.onerror = function(e) {
       //当连接发生error时触发
        console.log(e);
    };
}else{
    console.log("不支持SSE");
}
// 后端单向发送数据:以nodejs为例
res.writeHead(200, {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    "Access-Control-Allow-Origin": "*" //允许跨域
});
var num =0;
function sendData(){
   if(num===10){
      res.end();
   }else{
    res.write("id: " + num + "\n");
    res.write("data: " + num + "\n\n");
    num++;
   }
   // 定时发送数据
   setTimeout(sendData,1000);
}
sendData()

Node服务端代码实例

const http = require('http');
const 
SSE = require('sse');
// 多个客户端
var sseClients = [];
// 配置服务
var server = http.createServer(function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('okay');
});
server.listen(8080, '127.0.0.1', function() {
  var sse = new SSE(server, { path: '/test/xx', verifyRequest: (req) => {
    return true;
  }});
  sse.on('connection', function(client) {
    client.on('close', function() {
      let index = sseClients.indexOf(client);
      if (index > -1) {
        sseClients.splice(index, 1);
      }
    });
    sseClients.push(client);
    client.send('Hello world');
    client.count = 1;
    // 定时发送信息
    setInterval(() => {
      sseClients.forEach(function (item, index) {
        item.send(`[${sseClients.length}]服务端推送给客户端${index} : ${item.count}`);
        item.count++;
      });
    }, 1000);
  });
});

优缺点👇

  • 优点:不用每次建立新连接,延迟较低;SSE和WebSocket相比,最大的优势是便利,服务端不需要其他的类库,开发难度较低。
  • 缺点:如果客户端有很多,那就要保持很多长连接浏览器一直转圈,这会占用服务器大量内存和连接数。