面试官:我们平常的网页都是前端发消息,后端响应。后端可以主动发消息给前端吗?
我:emmm.....
面试官:感谢参加面试,如果有后续流程会通知你。
在等待结果的这段时间,我选择“脱去长衫”,去送外卖。
服务端消息推送(SSE,Server-Sent Events),是指服务器主动向客户端推送数据。常见的技术包括以下六种:
1. 短轮询(Short Polling)
第一天送外卖。没经验,于是我每隔 5 分钟就去商家问一下有没有订单要送,结果白跑了很多趟。我又尝试把时间间隔拉长到每小时去一次,结果错过了好多订单。
前端以一定的时间间隔向后端发请求,后端收到请求后立即返回最新的数据给前端。
- 优缺点:优点是实现
简单;缺点是不够实时,需要客户端不断发送请求,浪费带宽和服务器资源。 - 适用场景:适用于对实时性要求不高的场景,例如定时更新页面数据。
- 示例代码:
// 前端 定时调用接口
setInterval(function() {
fetch('/api/data').then(response => response.json()).then(data => {
// 处理数据
})
}, 1000)
// 后端 正常的http服务,不用做任何特殊处理
app.get('/api/data', (req, res) => {
const data = fetchData() // 获取数据
res.send(data)
})
2. 长链接(Long Polling)
第二天送外卖。吸取了上次的经验,这回我不来回跑了,来到商家店里我直接找把椅子坐下来,不接到订单我不走。
前端向后端发送请求,后端不立即返回数据,而是保持连接,直到有新数据才返回给前端,然后前端再发起新的请求。
- 优缺点:优点是可以减少不必要的请求,实现
相对实时的推送;缺点是服务器需要保持连接,消耗资源。 - 适用场景:对实时性要求较高的场景,例如网页版微信用的就是这种方式。
- 示例代码:
// 前端 每次得到返回结果后,重新发起请求。
function longPolling() {
fetch('/api/data').then(response => response.json()).then(data => {
// 处理数据
longPolling() // 递归调用实现长轮询
})
}
longPolling()
// 后端 并非立即返回,而是等待一定的时间再返回,等多长时间取决于具体需求。
app.get('/api/data', (req, res) => {
const data = fetchData() // 获取数据
if (data) {
res.send(data)
} else {
setTimeout(() => {
longPolling()
}, 1000) // 递归调用实现长轮询
}
})
3. SSE
第三天送外卖。昨天的方案虽然不累,也能及时接到订单,但有一个问题,老板店里的椅子实在是不舒服,店老板也不喜欢我在他店里占座。于是今天店老板给了我一个神奇铃铛,他一有订单,这个铃铛就会响,我再赶过来接单。
在一次 http 请求里,后端不断地发送数据,前端不断地接收数据,直到连接关闭。
- 优缺点:优点是实现
简单,支持服务器向客户端推送数据;缺点是只支持单向通信,数据量较小。 - 适用场景:适用于需要实时推送少量数据的场景,例如股票价格、天气预报等。
- 示例代码:
// 前端 不断接受数据
const eventSource = new EventSource('/api/events')
eventSource.onmessage = event => {
const data = JSON.parse(event.data)
// 处理数据
}
// 后端 不断发送数据
app.get('/api/events', (req, res) => {
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
})
setInterval(() => {
const data = fetchData() // 获取数据
const eventData = `data: ${JSON.stringify(data)}\n\n`
res.write(eventData)
}, 1000)
})
4. WebSocket 协议
第四天送外卖。我发现了一个叫饿**的蓝色软件!通过这个软件,我可以和店老板互相发消息,这样如果我不方便接单也能及时通知店老板。
改造 http 协议,前后端建立双向通信的长连接,可以随时发送和接收数据。(通俗来说,就是用 HTTP 协议传输非 Html 数据)
- 优缺点:优点是支持
双向通信,实时性好,适用于实时交互应用;缺点是实现较为复杂。 - 适用场景:适用于需要实时双向通信的场景,例如在线游戏、实时编辑等。
- 示例代码:
// 前端
const socket = new WebSocket('ws://localhost:3000')
socket.onopen = () => {
console.log('Connected')
}
socket.onmessage = event => {
const data = JSON.parse(event.data)
// 处理数据
}
// 后端
const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 3000 })
wss.on('connection', ws => {
console.log('Connected')
ws.on('message', message => {
console.log(`Received message: ${message}`)
})
setInterval(() => {
const data = fetchData() // 获取数据
ws.send(JSON.stringify(data))
}, 1000)
})
5. WebRTC 协议
第五天送外卖。送餐时候,我和一个顾客聊天,发现我累死累活只挣个跑腿钱,顾客花了大价钱却吃不到好东西,钱全让餐厅老板和平台挣去了!于是我和他商量,他直接给我钱,我每天定时给他做饭送饭,不要中间商赚差价!
替换 http 协议,webRTC 是浏览器提供的实时通信技术,支持浏览器之间直接进行点对点通信,例如音视频通话等。
- 优缺点:优点是支持浏览器之间直接
点对点通信,实现实时视频、音频通话等功能;缺点是需要使用者之间都支持 WebRTC。 - 适用场景:适用于需要实现浏览器之间直接点对点通信的场景,例如音视频通话、文件共享等。
- 实例代码有点多,可以参考这篇文章
6. Http2 WebPush(未成标准)
第六天送外卖。之前的店老板又找到我,说他们店开发了一种新的神奇铃铛,我去看了看,和之前的铃铛(SSE)相比只是更好看了,没有实质改变。
后端向前端发送推送通知,及时地通知前端有新的数据可用。前端可以通过service worker来接收推送通知。
- 优缺点:相当于在 SSE 的基础上,可以传输更多的数据。
- 适用场景:适用于需要实时推送大数据量的场景,例如新闻推送、音乐推送等。
- 示例代码:
// 前端
// 请求推送许可
const permission = await Notification.requestPermission();
if (permission !== "granted") return;
// 注册 Service Worker
const serviceWorker = await navigator.serviceWorker.register('/service-worker.js');
// 订阅推送
const subscription = await serviceWorker.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: 'your_public_key' // 从后端获取公钥
});
// 发送订阅信息到后端保存
const response = await fetch('/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: {
'Content-Type': 'application/json'
}
});
// 后端
const webpush = require('web-push');
// 设置VAPID参数
webpush.setVapidDetails(
'mailto:your_email@example.com',
'your_public_key',
'your_private_key'
);
// 处理订阅请求
app.post('/subscribe', (req, res) => {
const subscription = req.body;
// 存储订阅信息
// ...
// 发送推送通知
webpush.sendNotification(subscription, 'Your message');
res.status(201).json({});
});
今天送的最后一单外卖,发现客户恰好是之前的面试官!我和他分享了这几天的经历和对SSE的理解。他说干得不错,回去记得查收邮件,准备下一轮面试。
声明:本文尊重所有的劳动者,引用送外卖的例子只是为了形象地描述SSE相关技术。无论从事什么工作,只要是通过自己的劳动为社会做出贡献的人,都值得我们尊重和感激。