Wlxyzxyz's Blog

实时更新数据的几种方式

实时更新数据的几种方式

主要有轮询、Comet、Server-Sent Events 以及 WebSocket。比较好的方案是Server-Sent Events 和 WebSocket。

websocket

websocket 可以双向建立连接。在现代浏览器上可以轻松使用客户端,如果不行的话,也有SockJS库可以使用。

Deno的写法,使用Deno.upgradeWebSocket(req)来提升协议(协议升级机制)。使用websocket的请求头会带有upgrade:"websocket"的字段。

import { serve } from "https://deno.land/[email protected]/http/server.ts";

serve((req) => {
  const upgrade = req.headers.get("upgrade") || "";
  if (upgrade.toLowerCase() != "websocket") {
    return new Response("request isn't trying to upgrade to websocket.");
  }
  const { socket, response } = Deno.upgradeWebSocket(req);
  socket.onopen = () => console.log("socket opened");
  socket.onmessage = (e) => {
    console.log("socket message:", e.data);
    socket.send(new Date().toString());
  };
  socket.onerror = (e) => console.log("socket errored:", e.message);
  socket.onclose = () => console.log("socket closed");
  return response;
});

客户端实现方式。直接新建WebSocket,并使用send发送,onmessage接收。

const exampleSocket = new WebSocket("wss://www.example.com/socketserver", ["protocolOne", "protocolTwo"]);
exampleSocket.send("Here's some text that the server is urgently awaiting!");
exampleSocket.onmessage = function (event) {
  console.log(event.data);
}

server-sent events

如果只需要服务端推送的话,这个方案比较简单,但是它只能单向传递(从服务器到客户端)。

Deno的写法,可以用setInterval和自己来构造一个Response。

import { serve } from "https://deno.land/[email protected]/http/server.ts";

const msg = new TextEncoder().encode("data: hello\r\n\r\n");

serve(async (_) => {
  let timerId: number | undefined;
  const body = new ReadableStream({
    start(controller) {
      timerId = setInterval(() => {
        controller.enqueue(msg);
      }, 1000);
    },
    cancel() {
      if (typeof timerId === "number") {
        clearInterval(timerId);
      }
    },
  });
  return new Response(body, {
    headers: {
      "Content-Type": "text/event-stream",
    },
  });
});

如果使用的是fresh框架,只返回body会出现错误“An error occured during route handling or page rendering. Error: This page does not have a component to render.”

get参数里面只使用req而不要使用ctx,或者提供一个渲染的函数。

/** @jsx h */
import { h } from "preact";
import { Handlers } from "$fresh/server.ts";
const msg = new TextEncoder().encode("data: hello\r\n\r\n");

export const handler: Handlers = {
  async GET(req, ctx) {
    const resp = await ctx.render();
    let timerId: number | undefined;
    const body = new ReadableStream({
      start(controller) {
        timerId = setInterval(() => {
          controller.enqueue(msg);
          console.log("timer running")
        }, 1000);
      },
      cancel() {
        if (typeof timerId === "number") {
          clearInterval(timerId);
        }
      },
    });
    return new Response(body, {
      headers: {
        "Content-Type": "text/event-stream",
      },
    });
  },
};

export default function ServerSent() {
  return (
    <main>
    </main>
  );
}

客户端的写法。构造一个EventSource对象。用onmessage监听事件。

const evtSource = new EventSource("url");
evtSource.onmessage = function(event) {
  console.log(`message: ${event.data}`);
}

参考:

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Protocol_upgrade_mechanism https://deno.com/blog/deploy-streams https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API https://javascript.info/websocket https://xiaocaoge.com/server-sent-events/ https://segmentfault.com/a/1190000039376916 https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events