• Comet,SSE,WebSocket前后端的实现


    Comet(服务器推送)的两种方式

    短轮询

    页面定时向服务器发送请求, 步骤为:建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接

    //前端js
      var xhr = new XMLHttpRequest();
      setInterval(()=>{
        xhr.onreadystatechange = function () {
          if (xhr.readyState == 4) {
            let result = xhr.responseText
            console.log(result);
          }
        }
        xhr.open('get', '/front/test');
        xhr.send(null);
      },3000)
    }
    
    //后端 node
    const Koa = require('koa');
    const app = new Koa();
    const router = require('koa-router')()
    const koaBody = require('koa-body');
    
    router.get('/front/test', koaBody(), ctx=>{
      // ctx.set('Connection', 'close');  不设置response.header,默认长连接,否则为短链接
      ctx.body = { title: 'chuchur'}
    });
    app.use(router.routes());
    
    app.listen(7005, () => {
      console.log('server run in http://localhost:7005');
    });
    
    

    长轮询

    长轮询的方式是,页面向服务器发起一个请求,服务器一直保持tcp连接打开,知道有数据可发送。发送完数据后,页面关闭该连接,随即又发起一个新的服务器请求,在这一过程中循环,步骤为:建立连接——数据传输...(保存连接)...数据传输——关闭连接

    var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
          if (xhr.readyState == 4) {
            let result = xhr.responseText
            console.log(result);
            xhr.open('get', '/front/test');	//在获得数据后重新向服务器发起请求
            xhr.send(null);
          }
        }
        xhr.open('get', '/front/test');
        xhr.send(null);
    

    短轮询和长轮询的区别是:短轮询中服务器对请求立即响应,而长轮询中服务器等待新的数据到来才响应,因此实现了服务器向页面推送实时,并减少了页面的请求次数。

    http流

    //前端
    var xhr = new XMLHttpRequest();
    var received = 0;	//最新消息在响应消息的位置
    xhr.onreadystatechange = function () {
      if (xhr.readyState == 3) {
        let result = xhr.responseText.substring(received);
        console.log(result);
        received += result.length;
      } else if (xhr.readyState == 4) {
        console.log("完成消息推送");
      }
    }
    xhr.open('get', '/front/test');
    xhr.send(null);
    
    //node
    router.get('/front/test', koaBody(), ctx=>{
      ctx.set({
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
      });
      ctx.body = { title: 'chuchur'}
    });
    

    http流不同于上述两种轮询,因为它在页面整个生命周期内只使用一个HTTP连接,具体使用方法即页面向浏览器发送一个请求,而服务器保持tcp连接打开,然后不断向浏览器发送数据。

    SSE eventSource

    eventSource是用来解决web上服务器端向客户端推送消息的问题的。不同于ajax轮询的复杂和websocket的资源占用过大,eventSource(sse)是一个轻量级的,易使用的消息推送API ,大多数浏览器实现了SSE(Server-Sent Events,服务器发送事件) API,SSE支持短轮询、长轮询和HTTP流

    前端实现

    //生成EventSource对象,url必须同源
    var evtSource = new EventSource("/front/test");  
    //收到服务器发生的事件时触发,如果连接断开,还会自动重新连接
    evtSource.onmessage = function (e) {
      console.log(e.data);
    }
    //成功与服务器发生连接时触发
    evtSource.onopen = function () {
      console.log("Server open")
    }
    //出现错误时触发
    evtSource.onerror = function (e) {
      console.log("Error")
    }
    
    //自定义事件
    evtSource.addEventListener("test", function (e) {
      console.log(e.data);
    }, false)
    

    服务端实现

    //node
    router.get('/front/test', koaBody(), ctx=>{
      ctx.set({
        'Content-Type': 'text/event-stream',  //事件流的对应MIME格式为text/event-stream
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
      });
      ctx.body = 
      'id:11
    '+
      'retry: 100
    '+
      'event:test
    '+
      'data: hello word
    
    '
    });
    

    服务端返回数据需要特殊的格式,它分为四种消息类型:event, data, id, retry

    • event指定自定义消息的名称
    • data指定具体的消息体,可以是对象或者字符串
    • id为当前消息的标识符,可以不设置
    • retry设置当前http连接失败后,重新连接的间隔。EventSource规范规定,客户端在http连接失败后默认进行重新连接,重连间隔为3s,通过设置retry字段可指定重连间隔;

    每个字段都有名称,紧接着有个”:“。当出现一个没有名称的字段而只有”:“时,这就会被服务端理解为”注释“,并不会被发送至浏览器端,如: commision

    WebSocket 全双工通讯

    WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
    浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。

    前端实现

    let socket = new WebSocket('ws://localhost:7005/ws/test')
    
    // 建立连接
    socket.onopen = () => {
      console.log('connection')
      //发送消息,告诉服务器,我上线了.
      socket.send(JSON.stringify({ name: 'chuchur' + Date.now(), msg: '我来了' }))
    }
    //接收消息
    socket.onmessage = (evt) => {
      let data = JSON.parse(evt.data);
      console.log(data)
    }
    
    //socket 断开
    socket.onclose = () => {
      console.log('close')
    }
    //socket 发生错误
    socket.onerror = () => {
      console.log('error')
    }
    

    后端的实现

    //引入相关ws库
    ....
    const WebSocketServer = require('ws').Server;
    //此处定义变量
    const server = app.listen(7005, () => {
      console.log('server run in http://localhost:7005');
    });
    //拿到ws对象
    const wss = new WebSocketServer({ server });
    
    //链接成功
    wss.on('connection', ws => {
      console.log('有人来了') //当客户端连接之后
      //接收消息
      ws.on('message', data => {
        data = JSON.parse(data)
        console.log(data)
    
        if (data.name) {
          //广播,告诉大家,有人来了
          wss.broadcast(JSON.stringify({ msg: data.name + '来了,大家欢迎' }))
    
          //记录ID,可以实现客户端一对一,或一对多通讯,ID为唯一值
          ws.id = data.name
        }
        //实现一对一通话
        if (data.toID) {
          wss.clients.forEach(client => {
            if (data.toID == client.id) {
              client.send(JSON.stringify({ msg: ws.name + '对你说:' + data.msg }))
              return;
            }
          })
        }
      })
    
      //发送消息
      ws.send(JSON.stringify({ msg: '你来了, 奖励一颗糖' }));
    })
    
    //广播
    wss.broadcast = data => {
      wss.clients.forEach(client => {
        client.send(data);
      });
    };
    
    setInterval(() => {
      console.log('当前在线人数:' + wss.clients.size)
    }, 1000);
    

    ws转发配置

    //webpack 开发配置
    devServer: {
     ...
     proxy: {
          '/ws': {
              target: 'http://120.0.0.1:7005', //转发到node
              changeOrigin: true,
          }
      }
    }
    //nginx 部署
    server {
        listen      80;
        server_name localhost;
    
        # 处理WebSocket连接:
        location ^~ /ws/ {
            proxy_pass         http://127.0.0.1:7005;
            proxy_http_version 1.1;
            proxy_set_header   Upgrade $http_upgrade;
            proxy_set_header   Connection "upgrade";
        }
    
        # 其他所有请求:
        location / {
            proxy_pass       http://127.0.0.1:7005;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
    

    关于更多的ws相关配置,可以参考 WS
    开发过程中需要自行做转发

    总结

    • ajax短轮询:省事,最简单,不论服务端是否返回数据,埋头苦干,任劳任怨
    • 长轮询:也是埋头,只不过是,拿到数据才做出反应. 服务器有个阻塞的过程.
    • SSE:可以接收服务端推送.接收http流,双向可控
    • Socket:全双工通讯,功能强大, 耗资源

    各有优缺点, 主要是看什么场景用什么.

  • 相关阅读:
    小米智能家居接入智能家居平台homeassistant的方法
    我的nodejs 快速入门
    node.js JS对象和JSON字符串之间的转换
    Mac安装搭建sublimeText3开发Nodejs环境
    使用Xcode IDE写node.js
    nodejs中exports与module.exports的区别
    安装pysqlite2
    linux 终端分屏命令
    MQTT学习笔记
    Cache缓存
  • 原文地址:https://www.cnblogs.com/chuchur/p/10462336.html
Copyright © 2020-2023  润新知