At the Forge - HTML5 中的通信

作者:Reuven M. Lerner

上个月,我对 HTML5 进行了初步了解,HTML5 是 HTML 的新标准,越来越受到流行浏览器的支持。HTML5 包括大量不同的技术和解决方案,并且正在以零散的方式推出。HTML 的某些部分,例如文档顶部的缩写 DOCTYPE 声明,已经可以使用。其他部分,例如 HTML 表单的新元素,尚未完全准备好用于主流用途,除非您愿意使用 JavaScript 来处理那些不支持这些功能的浏览器中的元素。

对我来说,改进的表单标签和用于节、标题等的扩展语义标记都是重要且有用的进步。但在许多方面,我认为一些最重要的改进更难理解,并且需要更长的时间才能流行起来。这些改进涉及同一浏览器中不同页面之间如何相互通信,或 Web 浏览器如何与外部资源通信。

迄今为止,对于在浏览器中运行的程序(即使用 JavaScript)来说,在两个数据源之间创建混合或与另一个打开的窗口交互一直很困难,如果不是不可能的话。这是由于历史、实践和安全方面的多种考虑因素造成的,所有这些都是可以理解的。但是今天,我们希望我们的浏览器做更多的事情。实际上,大多数现代应用程序都是为浏览器开发的,如果我们能够以某种方式将更多信息和智能推送到浏览器,我们将减少服务器的负载并提高应用程序的响应速度。

HTML5 中的许多功能——是的,其中一些功能实际上并不是 HTML5 规范的一部分,但为了本专栏的目的,我将假装它们是——旨在精确地解决这个问题。这些功能并非旨在使创建网页更容易,而是为了创建 Web 应用程序。具体来说,我谈论的是页面间通信、WebSockets 和线程,也称为 Web Worker。这些主题中的每一个都可能值得单独写一篇专栏文章,因此我承认我在此处提供的示例旨在让您了解可能实现的功能,而不是全面的教程或示例。尽管如此,我希望您会对这里提出的可能性感到兴奋,甚至可能会想到利用这些功能的新颖而有趣的方法。

页面间通信

Web 的基本构建块是页面,更具体地说,是 HTML 页面。当然,现代 Web 浏览器可以显示各种不同的格式,但就所有意图和目的而言,当您谈论 Web 应用程序时,您谈论的是一个或多个 HTML 页面。如今,可以使用 JavaScript 修改该页面,使用各种不同的技术,这些技术通常与 Ajax 名称混为一谈,无论这是否真实或合适。Ajax 使事情变得有些复杂,消除了过去存在于 HTTP 请求和 HTML 页面之间的一一对应关系。尽管这意味着我们应用程序的流程变得更加复杂,但 Ajax 使 Web 成为对用户甚至对开发人员来说都更有用和友好的平台。

多年来,Web 浏览器都是每个窗口一个页面的形式。如果您想同时浏览三个页面,则需要打开多个窗口。现代浏览器都支持选项卡的使用,有些用户(像我一样)滥用此功能非常严重,每天打开数十个选项卡,然后慢慢花时间阅读和关闭每个选项卡的内容。鉴于所有这些选项卡(或窗口)都在同一程序中运行,它们应该可以使用 JavaScript 和 HTML 的组合进行通信。

也许应该如此,但迄今为止,这还不可能。这是由于隐私问题。您不希望一个网页能够在未经允许的情况下读取或写入另一个网页,并且没有提供此类权限的标准。这不仅适用于两个单独的页面,也适用于同一页面上的两个 iframe,它们可能希望相互影响。

现在,如果您认为您实际上过去已经能够做到这一点而没有任何大惊小怪,那么您可能是对的。确实,iframe 可以与其父窗口通信,甚至可以修改父窗口,但这仅在两个页面具有相同的来源时才成立。HTML5 通过允许页面相互通信来改变游戏规则,即使它们具有不同的来源。

其工作原理如下。发送页面在应接收消息的窗口或 iframe 上调用 postMessage() 方法,以及接收者的预期来源。例如,让我们创建一个非常简单的 HTML 文件,其中仅包含一个 iframe(列表 1)。目前,请忽略那里定义的 JavaScript 事件处理程序。我稍后会讲到。

列表 1. atf.html,容器 HTML 页面

<!DOCTYPE html>
<head>
<title>Page title</title>
<script src="jquery.js"></script>

<script>
   $(document).ready(function() {
   window.addEventListener('message', receiver, false);
   function receiver(e) {
       alert("origin = '" + e.origin + "'");
       alert("data = '" + e.data + "'");
           $("#message").val(e.data);
   };
   });
</script>

</head>
<body>
<h1>Page headline</h1>
<p id="message">[No message yet]</p>

<iframe id="my-iframe" src="iframe.html" />
</body>
</html>

此页面内部是一个 iframe。现在,出于演示目的,此 iframe 将与外部页面具有相同的来源。但在许多情况下,iframe 将来自完全不同的来源。在 HTML5 中,这根本无关紧要。您可以向您喜欢的任何接收者发送消息。如果您查看 iframe.html 的 HTML 源代码,您将了解如何实现这一点

$(document).ready(function() {
$("#send-button").click(function() {
    window.parent.postMessage($("#text-to-send").val(), '*');
});
});

在此示例中,我使用 jQuery 来获取 ID 为“send-button”的按钮。然后,我向该按钮添加一个事件处理程序,指示当单击该按钮时,它应调用 window.parent.postMessage,发送文本字段中包含的文本。我应该注意到,可以在任何窗口或 iframe 上调用 postMessage() 方法,并且它可以发送其第一个参数中的任何文本。

列表 2. iframe.html,其内容加载到 iframe 中

<!DOCTYPE html>
<head>
<title>iframe title</title>
<script src="jquery.js" /></script>
</head>
<body>
<h1>iframe headline</h1>

<p>Text to send: <input type="text" id="text-to-send" /></p>
<p><input id="send-button" type="button" value="Send it!" /></p>

<script>
$(document).ready(function() {
   $("#send-button").click(function() {
       window.parent.postMessage($("#text-to-send").val(), '*');
   });
});
</script>


</body>
</html>

第二个参数指示您向其发送此消息的接收者的来源。在本例中,我已通过指定通配符来指示接收者可能具有任何来源。在生产环境中,可以安全地假设您将要指定来源。通过声明接收者的来源,会增加一些额外的安全性——仅当接收窗口对象的内容来自声明的来源时,才会发送消息。

在接收端,已发布的消息作为事件到达,接收者可以(并且应该!)在使用前检查该事件。回到 atf.html,您将看到接收者如何在事件处理程序中接受消息

$(document).ready(function() {
window.addEventListener('message', receiver, false);
function receiver(e) {
    alert("origin = '" + e.origin + "'");
    alert("data = '" + e.data + "'");
        $("#message").text(e.data);
};
});

此页面的事件处理程序指示它愿意接受消息。每条消息由两部分组成:消息(发送者作为参数传递给 postMessage 的文本字符串)和来源(发送者的来源)。请注意,发送者无法设置其来源;此信息由浏览器处理。

由于来源信息与消息一起传递,因此接收者可以过滤掉它愿意接受哪些来源。换句话说,尽管流氓站点可能会尝试开始向您可能在其他站点上打开的随机窗口发布消息,但此类消息实际到达的唯一方式是接收者愿意接受它们。我确信某个具有比我更黑暗心态的人会找到击败此安全机制的方法,但据我所知,它经过了非常仔细和巧妙的思考,并且应该避免大多数恶作剧。

既然任何窗口都可以向任何其他窗口发送消息,您可以用它做什么呢?当然,答案是没有人知道。根据我的想法,我可以想象聊天客户端——或更笼统地说,使用 Web 浏览器上的单个窗口作为通信交换机和信息交换中心——抓取源和传入消息并将它们放在适当的页面上。想象一下,如果 Facebook 有一个单独的 iframe,它可以处理其(非常大的!)与服务器的交互次数,然后通过该 iframe 处理所有页面更新,而不是在每个单独的窗口或选项卡上。

我还可以想象 postMessage() 方法将迎来多窗口、类似桌面应用程序的新时代。想想现在有多少桌面应用程序使用多个窗口——一个用于控制,另一个用于每个文档,还有一个用于“调色板”选项。现在您可以使用 Web 浏览器和本机消息传递接口来做同样的事情。

人们将如何利用这些功能尚不清楚,但我预测我们将看到大量利用这些功能的新型、丰富的、基于浏览器的应用程序。

WebSockets

UCB(伯克利)对 UNIX 操作系统最伟大的贡献之一是套接字的引入。套接字允许程序员轻松快速地打开与另一台计算机的连接。一旦打开,套接字的操作方式类似于点对点文件句柄,使您可以忽略套接字中的数据正在通过数十或数百台其他计算机传输的事实。从 SMTP 到 FTP 再到 HTTP,大量的 Internet 服务都使用套接字。我个人已经使用它们近 20 年,从我的本科论文到 Web 浏览器和服务器,再到各种支持 Internet 的应用程序,来实现各种功能。

HTML5 使用一种称为 WebSockets 的技术,将类似套接字的连接带入浏览器。WebSockets 在原理上类似于 UNIX 套接字,因为您可以打开与 Internet 上任意其他点的连接,并可靠地发送和接收数据,而无需考虑沿途的众多跃点或连接。

现在,如果您是一位经验丰富的 Web 开发人员,您可能会想知道这有什么大不了的。毕竟,Ajax 调用允许您打开 HTTP 连接并发送和接收数据。并且 xhr(XmlHttpRequest 函数)已经存在几年了,并且运行良好。不同之处在于 WebSockets 将允许您打开与 Internet 上任何位置的一个或多个连接,而不仅仅是与具有与当前页面相同来源的服务器的连接。此外,WebSockets 使用自己的协议,该协议确实与 HTTP 非常相似,但开销要少得多。最后,WebSockets 保持打开状态,只要双方同意这样做——与 HTTP 不同,HTTP 旨在是无状态的,并在单个请求-响应事务后关闭。由于所有这些原因,使用 WebSockets 进行通信通常会更有效率。许多描述 WebSockets 的文章都做了数学计算,并表明 WebSockets 比 HTTP 有多高效——而且差异是惊人的。

使用 WebSockets 非常简单。您可以使用一些 JavaScript 代码打开 WebSocket,通常(据推测)在用户执行操作(例如按下按钮)或发生特定事件(例如,经过一定时间)时触发。无论如何,您都可以通过指定要连接的 URI 来打开新的 WebSocket,从协议名称(ws 或 wss,分别用于未加密或加密)开始,继续使用主机名,然后以资源名称结尾。

列表 3. ws.html

<!DOCTYPE html>
<head>
<title>Page title</title>
<script src="jquery.js"></script>

<script>
   var weatherSocket = new WebSocket("ws://127.0.0.1:8080");

   $(document).ready(function() {


   weatherSocket.onopen = function(e) {
   alert("Opened weather socket");
   };

   weatherSocket.onmessage = function(e) {
   alert("Received a message: " + e.data);
   };

   weatherSocket.onclose = function(e) {
   alert("Closing the weather socket...");
   };

   });
</script>

</head>
<body>
<h1>Page headline</h1>
<p>WebSockets!</p>

<script>

   while(weatherSocket.readyState == 0)
   {
   alert("socket state is " + weatherSocket.readyState);
   }

   alert("socket state is " + weatherSocket.readyState);

   weatherSocket.send("Hello from the client!");
   alert("socket state is " + weatherSocket.readyState);
   weatherSocket.close();
</script>

</body>
</html>

一旦 WebSocket 打开,您可以向其附加回调,指示在套接字打开、关闭或接收到消息时应发生什么。(每次 WebSocket 从远程主机接收数据时,它都会调用“onmessage”回调函数。)

例如,这是一个简单的 WebSocket,它从假设的天气服务器检索数据

var weatherSocket = new WebSocket("ws://127.0.0.1:8080"); 
 ↪// Our own weather server

然后,您可以分配回调

weatherSocket.onopen = function(e) {
alert("Opened weather socket");
};

weatherSocket.onmessage = function(e) {
alert("Received a message: " + e.data);
};

weatherSocket.onclose = function(e) {
alert("Closing the weather socket...");
};

最后,您可以通过调用 send() 方法发送消息——是的,就是您在上面看到的相同方法,但没有指示来源的第二个参数。

请注意,虽然您使用 send 直接写入 WebSocket,但您不会直接从它读取结果,也不会通过 send() 的返回值读取结果。相反,您将通过执行 weatherSocket.onmessage() 处的方法来获取发送给您的数据。

此描述中缺少一个部分,即 WebSocket 连接到的服务器。您无法连接到另一端的任何旧服务器,尤其是 HTTP 服务器。幸运的是,越来越多的软件包(以各种开源语言)可以处理 WebSockets 的服务器端。其中一个软件包是用于 Ruby 的 em-websocket gem,它基于著名的 eventmachine gem。WebSocket 服务器库已经存在于 PHP 和 Python 以及许多其他语言中。随着时间的推移,我希望看到大量与 WebSocket 兼容的服务器涌现。

您如何使用 WebSockets?与窗口间通信一样,我希望最好的应用程序和想法尚未开发出来。但是,一旦您的 Web 浏览器可以使用专门的高性能协议连接到 Internet 上的任何主机,您可以想象天空才是极限。突然之间,基于 Web 的聊天服务器不再需要使用笨拙的方法或黑客攻击来允许实时聊天。您可以在客户端而不是服务器上创建混合应用程序。结合 HTML5 中的新地理定位功能,您可以拥有一个实时更新您的位置的地图,仅使用 HTML 和 JavaScript 即可。这确实意味着在服务器端,Web 应用程序现在需要的不仅仅是安装 Apache,但这已经有一段时间了,因为应用程序变得越来越复杂,所以我认为您不必太担心这一点。

Web Worker

最后,HTML5 的另一个有趣的补充是 Web Worker 的概念,您可以将其视为浏览器的线程等效物。也许您有一项复杂的任务需要与页面渲染或从 Internet 下载信息并行处理。通过将工作分散到两个 Web Worker,您可以利用当今现代的多处理器计算机来获得更快的速度。由于 Web Worker 在后台运行,而不是在处理页面显示的线程上运行,因此页面应该比所有内容都在一个线程中运行时响应更快。

现在,我必须承认,我通常会尽量避免使用线程进行编程,因为当您拥有共享资源时,可能会出现许多问题。鉴于 JavaScript 从未被设计为与线程一起工作,当我听到 Web Worker 时,我的第一个想法是这如何才能在保持数据安全和浏览器稳定的情况下工作。该解决方案似乎是合理的,尽管对我(和许多其他人)来说,现在判断还为时过早。

想法是这样的。您可以通过创建一个新的 Worker 对象来启动 Web Worker

var worker = new Worker("code.js");

请注意,您将文件名传递给 Web Worker。您不能将一段代码直接传递给它,也不能通过传递函数引用来传递。也许最终基于浏览器的应用程序可以动态创建,然后在服务器上存储(使用 WebSockets?)文件,但此限制的主要目的是确保各种 Web Worker 之间没有共享数据的机会,从而避免传统上与线程相关的问题。

实际上,Web Worker 的运行方式几乎就像它们存在于不同的计算机上一样,它们之间没有直接连接。Worker 无法访问 DOM,这意味着页面上的任何元素。主线程中的函数和数据对 Web Worker 不可用,反之亦然。

这就引出了一个问题:主线程和 Web Worker 如何通信?答案可能不会让您感到惊讶。它们使用 postMessage(),这是一种消息传递机制,可用于将信息从一个窗口或选项卡发送到另一个窗口或选项卡,而与来源无关。

我可以预见 Web Worker 的许多用途。首先,它们将允许基于浏览器的应用程序一次处理多件事,从而确保主线程用于渲染 UI 和与用户交互。其次,这意味着您可以开始分解问题,利用现代计算机硬件,它可以智能地将不同的线程放在不同的处理器上。最后,这意味着 JavaScript 现在具有内置消息传递机制的开端。而且,尽管程序仍然必须保留在单个浏览器中,但我不得不假设在某个时候,不仅可以在本地计算机上打开 Web Worker,而且还可以在远程计算机上打开 Web Worker。

结论

Marc Andreessen,他是原始 Mosaic 浏览器的作者,也是 Netscape 的创始人之一,帮助普及了 Web,他多年前声称浏览器是新的操作系统。即使在过去几年中,Ajax 和其他先进的 Web 技术取得了进步,并且出现了 Google Docs 等令人惊叹的基于浏览器的应用程序,我仍然怀疑基于 Web 的应用程序是否真的能与它们的桌面应用程序相媲美。跨窗口通信、WebSockets 和 Web Worker 的添加在很大程度上让我相信 Andreessen 的预测几乎成真。

HTML5 及其相关技术为开发人员提供了丰富的新选择。这将需要一些时间来弄清楚这些技术的效果如何,如何绕过并非所有浏览器都支持它们的事实,以及各种功能可能有多么有用(或无用)。如果您是 Web 开发人员,我鼓励您尽快学习和使用这些技术。我已经因此更改了一些应用程序的架构,如果这也发生在您身上,我不会感到惊讶。

资源

我读过的关于该主题的最佳书籍,《深入 HTML5》,在撰写本文时甚至还不是一本真正的书,而是一个由 Mark Pilgrim(diveintohtml5.org)编写的免费在线资源。如果您熟悉 Pilgrim 之前的作品,例如《深入 Python》,您就会知道他的写作非常出色。毫不奇怪,这是我转向学习 HTML5 的第一个资源,并且它仍然是我最喜欢的教程和参考资料的组合。

然而,Pilgrim 的书对我在本月专栏中提到的主题几乎没有提及。对于这些主题的优秀教程,我推荐 Peter Lubbers、Brian Albers 和 Frank Salim 编写的《Pro HTML5 Programming》。最后一本书也是针对初学者的。尽管我认为其他书籍在其他方面比这本书更好,但它在本月我提到的案例中确实表现出色。

最后,请查看 www.html5rocks.com,这是一个 Google 赞助的网站,描述了各种与 HTML5 相关的技术,包括文档和代码示例。

Reuven M. Lerner 是一位资深的 Web 开发人员、架构师和培训师。他是西北大学学习科学专业的博士候选人,研究协作在线社区的设计和分析。Reuven 与他的妻子和三个孩子住在以色列的莫迪因。

加载 Disqus 评论