polling & SSE & websocket

154 阅读2分钟

背景

  • 需要将后台发生的变化主动地、实时地传送到浏览器端,而不需要用户手动地刷新页面
  • 例如:聊天室、实时的报价系统、在线游戏
  • http 缺陷:通信只能由客户端发起

polling 轮询

轮询是客户端和服务器之间会一直进行连接,每隔一段时间就询问一次

  • 浏览器实时显示服务器时间
<body>
    <div>
        时间:
    </div>
    <div class = "clock"></div>
    <script>
        function getTime() {
            let xhr = new XMLHttpRequest()
            xhr.open('get', '/polling')
            xhr.onload = () => {
                let content = xhr.response
                let clock = document.querySelector('.clock')
                clock.innerText = content
            }
            xhr.send()
        }
        getTime()
        setInterval(getTime, 1000)
    </script>
</body>
const Koa = require('koa')

const serve = require("koa-static");

const Router = require("koa-router");

// 时间格式化
const moment = require('moment')

const app = new Koa()

const router = new Router()

app.use(serve(__dirname + '/static'))

router.get('/polling', (ctx) => {
    ctx.body = moment().format()
})

app.use(router.routes())

app.listen(8080, () => {
    console.log('open 8080')
})

优点

实现简单,无需做过多的更改

缺点

  • 轮询的间隔过长,会导致用户不能及时接收到更新的数据
  • 轮询的间隔过短,会导致查询请求过多,增加服务器端的负担

SSE Server-Sent Events

实现原理

  • 服务器向客户端声明,接下来要发送的是流信息
  • 以流信息的方式,完成一次用时很长的下载
  • 流信息
    • 不属于一次性的数据包
    • 是个数据流,需要连续不断地发送过来
    • 客户端不会关闭连接,会一直等待

优点

  • 使用 HTTP 协议,现有的服务器软件都支持
  • 轻量级,使用简单
  • 默认支持断线重连
  • 支持自定义发送的消息类型

浏览器实时显示服务器时间

EventSource

developer.mozilla.org/zh-CN/docs/…

<body>
    时间 sse
    <div class="block"></div>
    <script>
      // 共用一个地址 & ip 前面的地址不用写
      const eventSource = new EventSource("/sse");

      eventSource.addEventListener("open", (e) => {
        console.log("open");
        console.log(e);
      });

      eventSource.addEventListener("message", (e) => {
        console.log("message");
        console.log(e);
        document.querySelector('.block').innerText = e.data
      });

      eventSource.addEventListener("sock", (e) => {
        console.log("sock");
        console.log(e);
        document.querySelector('.block').innerText = e.data
      });

      eventSource.addEventListener("error", (e) => {
        console.log("error");
        console.log(e);
      });
    </script>
  </body>
const http = require("http");
const fs = require("fs");
const moment = require("moment");

const server = http.createServer((req, res) => {
  // fs
  // 接口 sse
  if (req.url === "/sse") {
    // 必须设置成 event-stream 流信息
    res.setHeader("content-type", "text/event-stream");

    // 固定格式 data: xxxxx\n\n
    setInterval(() => {
      // 指定触发哪个东西
      res.write("event: sock\n")
      // 两个 \n 表示这条数据已经结束了,上下两行是一条数据
      res.write(`data: ${getDate()}\n\n`);
    }, 1000);

    // 一定不要写 end

    // sse
  } else {
    // 静态服务
    const readStream = fs.createReadStream("./static/index.html");
    readStream.pipe(res);
  }
});

function getDate() {
  return moment().format("MMMM Do YYYY, h:mm:ss a");
}

server.listen(3000);

websocket

特点

  • 双工通信
  • 允许跨域
  • 建立在 TCP 协议之上
  • 建立持久连接, 只需要一次 http 握手
  • 数据格式比较轻量,性能开销小,通信高效
  • 可以发文本,也可以发二进制数据
  • 没有同源限制
  • 协议标识符是 ws

缺点

  • 兼容性问题

协议升级

案例 - 浏览器实时显示服务器时间