写点什么

web messaging 与 Woker 分类:漫谈 postMessage 跨线程跨页面通信

发布于: 4 小时前

web messaging

  • 跨文档通信(cross-document messaging):跨就是我们国内更为熟知的 HTML5 window.postMessage()应用的那种通信;

  • 通道通信(channel messaging): 伴随着 server-sent 事件以及 web sockets, 跨文档通信和通道通信成为 HTML5 通信接口“套件”中有用的一部分。

window.postMessage

window.postMessage() 方法可以安全地实现跨源通信

iframe_contentWindow.postMessage(message, targetOrigin, [transfer]);

  • message 发送的数据,它将会被结构化克隆算法序列化。符串,结构对象、数据对象(如:File 和 ArrayBuffer)或是数组都是可以的。

  • targetOrigin:接受方。通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,字符串"*"(表示无限制)或者指定 URI。

  • transfer:Transferable 对象。 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

其他 window 可以监听分发的 message:window.addEventListener("message", callback, false);

window.postMessage 安全问题

如果您不希望从其他网站接收 message,请不要为 message 事件添加任何事件侦听器。 这是一个完全万无一失的方式来避免安全问题。

如果您确实希望从其他网站接收 message,请始终使用 origin 和 source 属性验证发件人的身份。无法检查 origin 和 source 属性会导致跨站点脚本攻击。—— 任何窗口都可以向任何其他窗口发送消息,并且您不能保证未知发件人不会发送恶意消息。 但是,验证身份后,您仍然应该始终验证接收到的消息的语法。 否则,您信任只发送受信任邮件的网站中的安全漏洞可能会在您的网站中打开跨网站脚本漏洞。

使用 postMessage 将数据发送到其他窗口时,始终指定精确的目标 origin,而不是*

无法检查 origin 和 source 属性会导致跨站点脚本攻击。


worker.postMessage

Worker 接口是Web Workers API 的一部分,代表一个后台任务,创建一个专用 Web worker,它只执行 URL 指定的脚本,并且在工作线程中执行。主从线程通过 postMessage 发送消息和 onmessage  onmessage  接受消息

worker 将运行在与当前 window不同的另一个全局上下文中,这个上下文由一个对象表示,标准情况下为DedicatedWorkerGlobalScope (标准 workers 由单个脚本使用; 共享 workers 使用SharedWorkerGlobalScope)。

除了无法读取 DOM 对象(包括:document、window、parent)、本地文件、对话框(alert/confirm/prompt),大部分 window 对象的方法和属性是可以使用的,如:  WebSocketsIndexedDB、 XMLHttpRequest 等,具体查看 Functions and classes available to workers 

Woker 分类

  • Shared Workers 一个 Shared Workers 可以被多个脚本使用——即使这些脚本正在被不同的 window、iframe 或者 worker 访问。,只要这些 workers 处于同一主域。共享 worker 比专用 worker 稍微复杂一点 — 脚本必须通过活动端口进行通讯(MessageChannel 的 port1 与 port2 间传递 )。详情请见SharedWorker

  • Broadcast Channel: 可以实现同  下浏览器不同窗口,Tab 页,frame 或者 iframe 下的 浏览器上下文 (通常是同一个网站下不同的页面)之间的简单通讯。

    在任何页面 new BroadcastChannel('mychannel')并 postMessage,其他页面的 BroadcastChannel 实例 onmessage 都能收收到消息

    它与 postMessage 的区别就是:BroadcastChannel 只能用于同源的页面之间进行通信,而 postMessage 却可以用于任何的页面之间的通信,换句话说,BroadcastChannel 可以认为是 postMessage 的一个实例,它承担了 postMessage 的一个方面的功能。


The principle of the Broadcast Channel API

  • Service Workers 一般作为 web 应用程序、浏览器和网络(如果可用)之间的代理服务。他们旨在(除开其他方面)创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动,更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步 API。

    Service worker 运行在 worker 上下文,因此它不能访问 DOM。相对于驱动应用的主 JavaScript 线程,它运行在其他线程中,所以不会造成阻塞。它设计为完全异步,同步 API(如 XHR 和 localStorage)不能在 service worker 中使用。

    不同于普通 Worker,Service Worker 是一个浏览器中的进程而不是浏览器内核下的线程(Service Worker 是走的另外的线程,可以理解为在浏览器背后默默运行的一个线程,或者说是独立于当前页面的一段运行在浏览器后台进程里的脚本。)因此它在被注册安装之后,能够被在多个页面中使用,也不会因为页面的关闭而被销毁。

    出于对安全问题的考虑,Service Worker 只能被使用在 https 或者本地的 localhost 环境下。

  • subworker: worker 能够生成更多的 worker。这就是所谓的 subworker(还是 Woker),它们必须托管在同源的父页面内。而且,subworker 解析 URI 时会相对于父 worker 的地址而不是自身页面的地址。这使得 worker 更容易记录它们之间的依赖关系。

  • Chrome Workers: It works exactly like a standard Worker,you can do so by using ChromeWorker instead of the standard Worker object。我的理解是 只是在 chrome 跑的 worker 。详情请见ChromeWorker


Woker 作用

Worker 作用在《浏览器层面优化前端性能(1):Chrom组件与进程/线程模型分析》里面讲过

  • 要尽量避免 JS 执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。Web Worker  异步优化下》

    创建 Worker 时,JS 引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作 DOM)

    JS 引擎线程与 worker 线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)

    JS 引擎是单线程的,这一点的本质仍然未改变,Worker 可以理解是浏览器给 JS 引擎开的外挂,专门用来解决那些大量计算问题。

  • SharedWorker是浏览器所有页面共享的,不能采用与 Worker 同样的方式实现,因为它不隶属于某个 Render 进程,可以为多个 Render 进程共享使用。所以 Chrome 浏览器为 SharedWorker 单独创建一个进程来运行 JavaScript 程序,在浏览器中每个相同的 JavaScript 只存在一个 SharedWorker 进程,不管它被创建多少次。

    页面 A 发送数据给 worker:window.worker.port.postMessage('get'),然后打开页面 B,调用 window.worker.port.postMessage('get'),即可收到页面 A 发送给 worker 的数据。

worker 属性与方法

postMessage(data, transferList);

  • data:发送的数据,会被 结构化克隆 ( structured clone)

  • transferList:Transferable 对象的数组,用于传递所有权。如果一个对象的所有权被转移,在发送它的上下文中将变为不可用(中止),并且只有在它被发送到的 worker 中可用。

  • 可转移对象是如 ArrayBuffer,MessagePort 或 ImageBitmap 的实例对象。transferList 数组中可默认不传,但不可传入 null。一般为 MessageChannel port

terminate()

立即终止 Worker 的行为. 本方法并不会等待 worker 去完成它剩余的操作;worker 将会被立刻停止

onmessage(event)  

Worker 接口的 onmessage 属性表示一个 EventHandler 事件处理函数,当 message 事件发生时,该函数被调用。这些事件所属 MessageEvent 类型,且当 Worker 子线程返回一条消息时被调用

  • event: event 对象  event.data 为  structured clone 数据

onerror()  onmessageerror 

  • onerror 特性是一个事件句柄,在 Workererror事件触发并冒泡时

  • onmessageerror 事件处理器接口是一个EventListener, 在 MessageEvent 类型的事件 messageerror 触发时调用 — 也就是说, 它收到的消息是不能进行序列化的 deserialized.


Woker 性能优化

worker.terminate()

使用完毕,为了节省系统资源,必须关闭 Worker

// 主线程worker.terminate();// Worker 线程self.close();
复制代码

worker.postMessage(arrayBuffer, [arrayBuffer])

主线程与 Worker 之间的通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是传址,Worker 对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给 Worker,后者再将它还原。这会造成性能问题!为了解决这个问题,JavaScript 允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做Transferable Objects

var ab = new ArrayBuffer(1);worker.postMessage(ab, [ab]);
复制代码

对于大的数据处理,不会产生性能负担。

同页面的 Web Worker

Worker 载入的是一个单独的 JavaScript 脚本文件,但是也可以载入与主线程在同一个网页的代码。减少网络加载耗时

<script id="worker" type="app/worker">  addEventListener('message', function () {    postMessage('some message');  }, false);</script><script>  var blob = new Blob([document.querySelector('#worker').textContent]);  var url = window.URL.createObjectURL(blob);  var worker = new Worker(url);  worker.onmessage = function (e) {    // e.data === 'some message'  };</script>
复制代码

先将嵌入网页的脚本代码(注意必须指定<script>标签的 type 属性是一个浏览器不认识的值),转成一个二进制对象,然后为这个二进制对象生成 URL,再让 Worker 加载这个 URL。

woker 在时间循环中执行顺序

worker 因为 JavaScript 新开一个线程,执行 worker 代码。shareWoker 因为不同 tab(一个 tab 一个进程),因而新开一个进程。

// 主线程let woker = new Worker('./test2.js');woker.onmessage = (res) => {  console.log(res.data);};setTimeout(()=>{  console.log('main')  setTimeout(()=>{    console.log('main2')    setTimeout(()=>{      console.log('main3')    },0)  },0)},0)// woker 线程,test2.jssetTimeout(() => {  self.postMessage('woker1');}, 0);self.postMessage('woker2');
复制代码

 输出顺序为,每次都不一样,以为有网络请求呀

main main2 woker2 main3 woker1

main main2 woker2 woker1 main3

main main2 main3 woker2 woker1

如果是桶页面内,顺序就机会不会变

<script id="worker" type="app/worker">setTimeout(() => {  self.postMessage('woker1');}, 0);self.postMessage('woker2');</script>
<script>// let woker = new Worker('./test2.js');var blob = new Blob([document.querySelector('#worker').textContent]);var url = window.URL.createObjectURL(blob);var worker = new Worker(url);worker.onmessage = (res) => {  console.log(res.data);};setTimeout(()=>{  console.log('main')  setTimeout(()=>{    console.log('main2')    setTimeout(()=>{      console.log('main3')    },0)  },0)},0)
</script>
复制代码

这个,还是 JavaScript 的 event loop 事件机制觉得,推荐阅读《弄懂javascript的执行机制:事件轮询|微任务和宏任务


在浏览器环境中,常见的 macro task 有 setTimeout、MessageChannel、postMessage、setImmediate。而常见的 micro task 有 MutationObsever 和 Promise.then。需要留意的是:

MessageChannel

Vue 中对于 macro task 的实现,优先检测是否支持原生 setImmediate,这是一个高版本 IE 和 Edge 才支持的特性,不支持的话再去检测是否支持原生的 MessageChannel,如果也不支持的话就会降级为 setTimeout 0。

MessageChannel用法

MessageChannel 创建了一个通信的管道,这个管道有两个端口[port1,port2],可以可以通过 postMessage 互相通信。

const channel = new MessageChannel();let port1 = channel.port1;let port2 = channel.port2;port1.onmessage = function (event) {  console.log("port1收到来自port2的数据:" + event.data);};port2.onmessage = function (event) {  console.log("port2收到来自port1的数据:" + event.data);};port1.postMessage("发送给port2");port2.postMessage("发送给port1");
复制代码

MessageChannel 用法很简单,但是功能却不可小觑。

MessageChannel 作用

web worker 间通信

多个 web worker 并想要在两个 web worker 之间实现通信的时候,MessageChannel 就可以派上用场:

var w1 = new Worker("worker1.js");var w2 = new Worker("worker2.js");var ch = new MessageChannel();w1.postMessage("initial port",[ch.port1]);w2.postMessage("initial port",[ch.port2]);w2.onmessage = function(e){      console.log(e.data);}
复制代码

通过 web worker 的 postMessage 方法把两个 MessageChannel 的 port 传递给两个 web woker,然后就可以通过每个 port 的 postMessage 方法传递数据了。

iframe 兄弟间通

传统 iframe 父子间通信:

var iframe1 = document.getElementById('iframe1');iframe1.postMessage(message, '*');
复制代码

使用 MessagePort.postMessage 方法把一条消息和 MessageChannel.port2 传递给 iframe。

var iframe1 = document.getElementById('iframe1');var iframe2 = document.getElementById('iframe2');iframe1.contentWindow.postMessage('main','*',[port1]);iframe2.contentWindow.postMessage('main','*',[port2]);
复制代码

代码地址:channel messaging basic demo 

worker_threads 兄弟线程通信

nodejs 的 MessageChannel 虽然与浏览器的,实现方式不同,但是用法相同,都是一个模型。在此列出,阐释这种思想。

const {isMainThread, parentPort, threadId, MessageChannel, Worker} = require('worker_threads');


通信事件 message 事件对象

Message 事件的定义可参见这里

在跨文档通信和通道通信中,lastEventId 的值一般是个空字符串;lastEventId 应用在服务器端发送事件上。发送信息中如果没有 ports, 则 ports 属性值就是个长度为 0 的数组。

MessageEvent 继承 DOM事件接口,且属性共享。然而,通信事件并没有冒泡,不能取消,也没有默认行为


Service Worker

前端缓存分析

前端缓存 大致可以分为 http 缓存 与 浏览器缓存

http 缓存推荐阅读《浏览器http缓存机制剖析:存储策略与过期策略的机理分析》,我们来分析下 浏览器缓存

storage

cookie、localStorage、sessionStorage

cookie 最大约为 4k,每个域名最多 50kcookie——不同浏览器限制不一样,一般用来存储关键数据(比如用户登录信息)

localStorage/sessionStorage 通常有 5MB 的存储空间,比如微信文章 不需要改动的资源(如 css/js)就基本存储在 localStorage 里面

推荐阅读《登录状态控制:cookies对比sessionStorage保持信息的分析

前端数据库:

WebSql 和 IndexDB,其中 WebSql 被规范废弃,他们都有大约 50MB 的最大容量,一般 当页面 store 的数据可以直接存储在里面。

manifest 缓存

已经被废弃,因为他的设计有些不合理的地方,他在缓存静态文件的同时,也会默认缓存 html 文件。这导致页面的更新只能通过 manifest 文件中的版本号来决定。所以,应用缓存只适合那种常年不变化的静态网站。如此的不方便,也是被废弃的重要原因。

推荐阅读《html5 离线缓存 manifest 详解》、《HTML5 离线存储实战之 manifest 的那些坑》

Service Worker

Service Worker 本质上也是浏览器缓存资源用的,只不过他不仅仅是 cache,也是通过 worker 的方式来进一步优化。

他基于 h5 的 web worker,所以绝对不会阻碍当前 js 线程的执行,sw 最重要的工作原理就是

  • 后台线程:独立于当前网页线程;

  • 网络代理:在网页发起请求时代理,来缓存文件;

这里不再赘述,再开一篇《ServiceWorker工作机制与生命周期:资源缓存与协作通信处理


参考文章:

MessageChannel 是什么,怎么使用? https://www.jianshu.com/p/4f07ef18b5d7

HTML5 postMessage iframe 跨域 web 通信简介 https://www.zhangxinxu.com/wordpress/2012/02/html5-web-messaging-cross-document-messaging-channel-messaging/

Web Worker 使用教程 www.ruanyifeng.com/blog/2018/07/web-worker.html

转载本站文章《web messaging与Woker分类:漫谈postMessage跨线程跨页面通信》,请注明出处:https://www.zhoulujun.cn/html/webfront/SGML/html5/2020_0615_8465.html

发布于: 4 小时前阅读数: 4
用户头像

还未添加个人签名 2021.06.25 加入

还未添加个人简介

评论

发布
暂无评论
web messaging与Woker分类:漫谈postMessage跨线程跨页面通信