因为数据业务的特殊性,有些计算数据服务端不能同步计算得出,需要通过异步方式计算出结果,然后再通知用户,这里我们可以使用SSE或者WebSocket,鉴于无需客户端和服务器双向通信,故采用SSE更加的简洁,技术更加友好,接下来我们就写一个node.js的实现,一个经过一段随机的时间,服务端向客户端发送一个随机的数字串。
效果如下:
一、SSE和WebSocket的几个重要区别
与 WebSocket
相比,EventSource
是与服务器通信的一种不那么强大的方式。
我们为什么要使用它?
主要原因:简单。在很多应用中,WebSocket
有点大材小用。
我们需要从服务器接收一个数据流:可能是聊天消息或者市场价格等。这正是 EventSource
所擅长的。它还支持自动重新连接,而在 WebSocket
中这个功能需要我们手动实现。此外,它是一个普通的旧的 HTTP,不是一个新协议。
二、客户端获取消息
三、搭建工程
项目目录如下,包含client端和node端,实现
client端index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Server Sent Events example 1</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<script type="module" src="./js/main.js"></script>
<link rel="stylesheet" href="./css/main.css" />
</head>
<body>
<h1>Server Sent Events example 1</h1>
<output id="sselog"></output>
</body>
</html>
client端main.js,主要用来接收消息
// create connection
const source = new EventSource("/random");
// SSE connection open
source.addEventListener("open", (e) => {
log("connected");
});
// SSE message
source.addEventListener("message", (e) => {
log("RECEIVED data:", e.data);
});
// SSE error or termination
source.addEventListener("error", (e) => {
if (e.eventPhase === EventSource.CLOSED) {
log("disconnected");
} else {
log("error", e.message);
}
});
const outlog = document.getElementById("sselog");
function log(...msg) {
msg.forEach((m) => (outlog.textContent += m + " "));
outlog.textContent += "\n";
outlog.scrollTop = outlog.scrollHeight;
}
node端static.js 主要用来展示消息,将res写入到index.html并write到浏览器展示
// serve static file from root directory
import path from "node:path";
import { access, stat, readFile, constants } from "node:fs/promises";
const mime = {
".html": "text/html",
".css": "text/css",
".js": "application/javascript",
err: "text/plain",
};
export async function serveStatic(res, uri, root) {
// get filename
let filename = path.join(root, uri);
// is file readable?
let isReadable;
try {
await access(filename, constants.R_OK);
isReadable = true;
} catch {}
if (!isReadable) {
serve(404, "404 Not Found\n");
return;
}
// is a directory?
const fileInfo = await stat(filename);
if (fileInfo.isDirectory()) filename = path.join(filename, "./index.html");
// read file contents
try {
const content = await readFile(filename);
serve(200, content, path.extname(filename));
} catch (err) {
serve(500, err.message);
}
// return content
function serve(code, content, type) {
res.writeHead(code, {
"Content-Type": mime[type] || mime["err"],
"Cache-Control": "must-revalidate, max-age=0",
"Content-Length": Buffer.byteLength(content),
});
res.write(content);
res.end();
}
}
node端index.js 借助http和url模块进行message的send
import http from "node:http";
import url from "node:url";
import { serveStatic } from "./static.js";
const port = 8000,
root = "./client/";
// start server
http
.createServer(async (req, res) => {
// get URI path
const uri = url.parse(req.url).pathname;
// return response
switch (uri) {
case "/random":
sseStart(res);
sseRandom(res);
break;
default:
await serveStatic(res, uri, root);
}
})
.listen(port);
console.log(`server running: http://localhost:${port}\n\n`);
// SSE head
function sseStart(res) {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
}
// SSE random number
function sseRandom(res) {
res.write("data: " + (Math.floor(Math.random() * 1000) + 1) + "\n\n");
setTimeout(() => sseRandom(res), Math.random() * 3000);
}
-- END --
课外阅读 (看到带着孩子送外卖的妈妈有感)
观刈麦⑴
田家少闲月,五月人倍忙。
夜来南风起,小麦覆陇黄⑵。
妇姑荷箪食⑶,童稚携壶浆⑷,
相随饷田去⑸,丁壮在南冈⑹。
足蒸暑土气,背灼炎天光⑺,
力尽不知热,但惜夏日长⑻。
复有贫妇人,抱子在其旁⑼,
右手秉遗穗⑽,左臂悬敝筐⑾。
听其相顾言⑿,闻者为悲伤⒀。
家田输税尽⒁,拾此充饥肠。
今我何功德⒂,曾不事农桑⒃。
吏禄三百石⒄,岁晏有余粮⒅。
念此私自愧⒆,尽日不能忘⒇。 [1]