在现代 Web 应用中,联网是不可避免的一个操作。然而,面对着庞大的用户群体和不稳定的网络环境,如何开发一个可扩展的联网应用成为一个亟待解决的问题。本文将介绍如何使用 Server-sent Events 和 Web Workers 来开发一个高效、可扩展的联网应用。
Server-sent Events(SSE)
Server-sent Events 是一种浏览器与服务器之间的持久连接,使服务器能够将数据推送给客户端。与 WebSocket 不同,SSE 不需要建立一个全双工连接,因此适合于那些不需要客户端与服务器之间的实时通信的场景,例如新闻提醒、股票行情等。
基础使用
下面我们来一步步实现一个最简单的 SSE 应用。
服务器端
首先我们需要设置一个简单的 HTTP 服务器:
-- -------------------- ---- ------- ----- ---- - --------------- ----- ------ - ----------------------- ---- -- - ----- ------- - - --------------- -------------------- -- ---- ---------------- ----------- -- --- ------------- ------------ -- ---- - ------------------ -------- -------------- -- - -- ----------- ---------------- ----- --------------------------- -- ----- -- ------------------- ---------------------- -- -----------------------
可以看到,在 HTTP 头部中我们设置了 Content-Type
为 text/event-stream
,告诉浏览器这是一条 SSE 消息。也可以设置 Cache-Control
为 no-cache
,告诉浏览器不要缓存这条消息。最后我们使用 setInterval
每隔一秒钟推送一次数据。
客户端
接下来在客户端建立一个 SSE 连接:
const eventSource = new EventSource('http://localhost:8080') eventSource.addEventListener('message', event => { console.log(event.data) })
这里我们使用了 EventSource
对象来建立 SSE 连接。我们给 EventSource
对象传入了服务器的 URL,当服务器推送一条消息时,我们就可以通过绑定 'message'
的事件回调来获得该消息的内容。
打开控制台,我们就可以看到每秒钟会输出一条类似 data: 2022-10-26T07:10:23.053Z
的日志。
事件类型
Server-sent Events 提供了三种事件类型:message
、open
和 error
。
当服务器向客户端推送一条消息时,客户端会触发 message
事件,可以通过 event.data
属性访问该消息的内容。
当 SSE 连接建立成功时,客户端会触发 open
事件。
当 SSE 连接发生错误时,客户端会触发 error
事件。
进阶用法
Server-sent Events 支持自定义事件类型和事件数据,类似于自定义事件。因此我们可以通过自定义事件类型来区分不同的 SSE 消息。
-- -------------------- ---- ------- -- ---- -------------- -- - ----------------- ------------------- ----- --------------------------- -- ----- -- --- -------------------------------------------- ----- -- - ---------------- --------------- ----------- --
可以看到,我们在服务器推送的消息中设置了事件类型为 notification
,客户端通过绑定 'notification'
的事件回调来处理该消息。
要注意的是,自定义事件类型必须以 'event:'
开头,否则浏览器不会认识。
浏览器支持
Server-sent Events 需要浏览器支持,以下为典型浏览器的支持情况:
- Chrome 6+
- Firefox 6+
- Safari 5+
- Opera 11.5+
- Edge 12+
Web Workers
Web Workers 是一种在后台运行 JavaScript 代码的机制,使得 Web 应用可以在不影响用户界面响应的情况下执行一些耗时的操作。与线程不同的是,Web Workers 并不能直接操作 DOM,因此是一种完全并发的机制。
基础使用
下面我们来一步步实现一个最简单的 Web Worker 应用:
主线程
首先,我们需要创建一个 Web Worker 对象,并为其绑定 'message'
事件。主线程可以通过 postMessage
方法向 Web Worker 发送消息,收到 Web Worker 回应的消息后,再通过 'message'
事件回调进行处理。
const worker = new Worker('./worker.js') worker.addEventListener('message', event => { console.log('Received message from worker:', event.data) }) worker.postMessage({ type: 'add', data: [1, 2] })
Worker 线程
Web Worker 对象会在一个单独的线程中运行,因此可以执行一些比较耗时的操作,例如计算一个向量的和。
-- -------------------- ---- ------- -- --------- --------------------------- ----- -- - -- ---------------- --- ------ - ----- ------ - -------------------- ------------------- - -- -------- -------- - ------ -------------- -- -- - - -- -- -
可以看到,我们在 Worker 线程中创建了一个事件监听器,并在收到主线程发送的消息时执行 add
函数,并通过 postMessage
方法把结果发送回主线程。
浏览器支持
Web Workers 也需要浏览器支持,以下为典型浏览器的支持情况:
- Chrome 4+
- Firefox 3.5+
- Safari 4+
- Opera 10.6+
- Edge 12+
进阶用法
Web Workers 提供了两种类型的线程:专用线程和共享线程。
专用线程
专用线程只能被创建它的脚本所使用,无法被其他脚本所共享。因此,它可以自由地读取和写入全局变量,而不用担心线程安全问题。
-- -------------------- ---- ------- -- --- ----- ------ - --- ------------------------------- ---------------------------------- ----- -- - --------------------- ------- ---- --------- ----------- -- -- ------ ----- ------------ - --- ---------------------------------- --------------------------------------------- ----- -- - --------------------- ------- ---- ------ --------- ----------- -- ------------------------- ----------------------------------------- -- -------------------- ----- ------ ----- --- -- --
可以看到,我们在主线程创建了一个专用线程,并在收到专用线程回应的消息后创建了一个共享线程,并把数据传递给共享线程。
共享线程
共享线程可以被多个脚本所共享。每个共享线程会有一个名为 port
的接口,可以用来建立与共享线程的联系。
// 共享 Worker addEventListener('connect', event => { const port = event.source port.addEventListener('message', event => { console.log('Received message from port:', event.data) port.postMessage(`Hello, ${event.data}!`) }) })
可以看到,我们在共享线程中创建了一个 connect
事件监听器,其中 event.source
就是代表连接到它的 port
对象。我们为 port
对象绑定了一个 'message'
事件回调,并在收到消息后返回一个带有 'Hello, '
前缀的回应。
使用 SSE 和 Web Workers 开发可扩展的联网应用
SSE 和 Web Workers 各自提供了一些独特的功能,结合使用可以有效地解决 Web 应用中的一些问题,例如:
- 避免不必要的轮询请求
- 分散计算负载
- 提高响应速度和吞吐量
下面我们来看一个具体的例子。
场景描述
假设我们正在开发一个实时聊天应用,用户在发送消息时可以选择包含一张图片。服务器在接收到用户的消息后需要对图片进行处理,例如根据图片识别人脸等等。
因为客户端可以包含一张或多张图片,因此处理这些图片可能会消耗大量的 CPU 资源。如果所有的处理都在服务器上进行,会导致服务器响应变慢,用户的聊天体验也会受到影响。
解决方案
我们可以将图片处理任务分发给 Web Workers,同时通过 SSE 把处理结果推送给客户端。
服务器端
-- -------------------- ---- ------- ----- ---- - --------------- ----- - ------------- --------- - - ----------------- ----- ------ - ----------------------- ---- -- - -- -- --- -- ----- ------- - - --------------- -------------------- ---------------- ----------- ------------- ------------ - -- ------------- --- -- -- ------------------------------ - ----- ------ - ----------------- ---- ----- --- - ----------------------- ----------------------------------- -- - -------------------- -- -- ------------------ - --------------- ------------ ---------------- ---------- -- ---------------------------------- -- - ---- -- ---------------------------- - ------------------ -------- --- ------- - - -------------- -- - --------- ----------------- ------------------- ---------------- -- ----- - -- ------------------- ---------------------- -- -----------------------
可以看到,我们在服务器处理请求的时候根据请求路径的不同来选择发送图片或 SSE 数据。
当客户端请求 '/image' 时,服务器会生成一张图片并发送给客户端。当客户端请求 '/sse' 时,服务器会建立 SSE 连接,并每秒钟推送一条消息。
客户端
-- -------------------- ---- ------- ----- ----------- - --- ---------------------------------------- -------------------------------------------- ----- ----- -- - -- ---- --- ------ ----- ----- ------ - --- --------------------- -------------------- ----- --------------- ----- -------- -- -- -- --- ------ ----- ----- ------ - ----- --- --------------- -- - ---------------------------------- ----- -- - ------------------- -- -- --------------------- --------- -------- ------- --
可以看到,我们在客户端建立了一个 SSE 连接,并绑定了 notification
事件回调。当收到服务器推送的消息时,我们创建一个 Web Worker 并向其发送数据。Web Worker 在处理完数据后会通过 postMessage
方法把结果发送回客户端,客户端再使用 Promise
等待结果。
Worker 线程
-- -------------------- ---- ------- --------------------------- ----- ----- -- - -- ---------------- --- --------------- - ----- -------- - ----- ---------------------- ----- ---- - ----- --------------- ----- ----- - ----- ----------------------- ----- ------ - --- ---------------------------- ------------- ----- --- - ----------------------- ---------- - ----------- -------------------- -- -- ----- ------ - ------------------ ------------------- - --
可以看到,我们在处理图片时使用了 OffscreenCanvas 来避免在主线程阻塞。具体地,我们向 Web Worker 发送图片的 URL,Web Worker 在收到数据后加载图片并进行模糊处理,最后使用 postMessage
方法把处理结果发送回主线程。
总结
本文介绍了如何使用 Server-sent Events 和 Web Workers 来开发可扩展的联网应用,并给出了具体的示例代码。
Server-sent Events 和 Web Workers 这两种技术都在现代 Web 应用开发中扮演着重要角色,能够帮助我们在不影响性能的情况下提高用户体验。但两者也有各自的限制,需要开发者根据实际情况选择恰当的技术方案。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/649bf9a148841e98948bcfde