Windows Consumer Preview 中的 WebSocket

在 Windows 8 Consumer Preview 和 Server Beta 中,IE10 和所有其他 Microsoft WebSocket 客户端和服务器目前均支持 IETF WebSocket 协议的最终版本。另外,IE10 还实施了 W3C WebSocket API 备选建议。

WebSocket 已发布稳定版本,开发人员可随时使用它来创建创新的应用程序和服务。本博文简要介绍了 W3C WebSocket API 及其底层的 WebSocket 协议。更新后的 Flipbook 演示使用了最新版本的 API 和协议。

我的上一篇博文介绍了 WebSocket 方案:

WebSocket 允许 Web 应用程序在浏览器中提供实时的通知和更新。开发人员在解决浏览器的原始 HTTP 请求-响应模型的限制方面遇到了诸多问题,因为该模型并非针对实时方案而设计。WebSocket 允许浏览器通过服务来开通双向、全双工的通信渠道。随后,双方即可使用该渠道立即向对方发送数据。现在,包括社交网站、游戏网站和财经网站在内的各类网站都可以提供更好的实时方案,并有望在不同浏览器之间使用相同的标记。

自 2011 年 9 月份的博文发布之后,工作组取得了重大进展。WebSocket 协议目前是 IETF 推荐的标准协议。另外,W3C WebSocket API 是 W3C 备选建议。

WebSocket API 简介(使用回显示例)

下面的代码段使用通过 ASP.NET 的 System.Web.WebSockets 命名空间创建的简单回显服务器,来回显从应用程序发送的文本和二进制消息。此应用程序允许用户键入要发送并回显为文本消息的文本或绘制可发送并回显为二进制消息的图片。

WebSocket 回显示例应用程序的屏幕截图。

有关对 WebSocket 和 HTTP 轮询之间的延迟和性能差异进行比较的复杂示例,请参阅 Flipbook 演示

连接到 WebSocket 服务器的详细过程

在本简单说明中,假设应用程序和服务器之间采用直接连接方式。如果已配置代理服务器,则 IE10 将通过向代理服务器发送 HTTP CONNECT 请求来开始该过程。

创建 WebSocket 对象之后,客户端和服务器将交换握手信号以建立 WebSocket 连接。

图中演示了从 HTTP 客户端到 HTTP 服务器的 HTTP GET 升级请求。

IE10 通过向服务器发送 HTTP 请求来开始该过程:

GET /echo HTTP/1.1

Host: example.microsoft.com

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

Origin: https://microsoft.com

Sec-WebSocket-Version: 13

我们来看一下该请求的各个组成部分。

连接过程从标准的 HTTP GET 请求开始,该请求允许请求遍历防火墙、代理服务器和其他中介:

GET /echo HTTP/1.1

Host: example.microsoft.com

HTTP Upgrade 标头请求服务器将应用层协议由 HTTP 切换为 WebSocket 协议。

Upgrade: websocket

Connection: Upgrade

服务器在其响应中转换 Sec-WebSocket-Key 标头中的值,以表明已遵从 WebSocket 协议:

Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

Origin 标头由 IE10 进行设置,以允许服务器强制实施基于源的安全性

Origin: https://microsoft.com

Sec-WebSocket-Version 标头标识所请求的协议版本。版本 13 是 IETF 推荐标准中的最终版本:

Sec-WebSocket-Version: 13

如果服务器接受升级应用层协议的请求,则会返回 HTTP 101 Switching Protocols 响应:

图中演示了从 HTTP 服务器客户端到 HTTP 客户端的 HTTP 101 Switching Protocols 响应。

HTTP/1.1 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

为表明已遵从 WebSocket 协议,服务器将对客户端请求中的 Sec-WebSocket-Key 执行标准转换,并在 Sec-WebSocket-Accept 标头中返回转换结果:

Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

然后,IE10 将对 Sec-WebSocket-Key 和 Sec-WebSocket-Accept 进行比较,以验证该服务器是 WebSocket 服务器,而不是经过伪装的 HTTP 服务器。

客户端握手已在 IE10 和服务器之间建立了基于 TCP 的 HTTP 连接。当服务器返回 101 响应之后,应用层协议将由 HTTP 切换为 WebSocket(使用之前建立的 TCP 连接)。

此时,HTTP 已完全无关。 使用轻型 WebSocket 连接协议,无论哪个端点都可以随时发送或接收消息。

图中演示了在两个 Websocket 之间发送的 WebSocket 消息。

连接到 WebSocket 服务器的编程

WebSocket 协议定义两个新的 URI 方案,这与 HTTP 方案颇为类似。

  • "ws:" "//" host [ ":" port ] path [ "?" query ] 根据“http:”方案建模。默认端口为 80。用于不安全(未加密)的连接。
  • "wss:" "//" host [ ":" port ] path [ "?" query ] 根据“https:”方案建模。默认端口为 443。用于通过传输层安全性建立隧道的安全连接。

当存在代理服务器或网络中介时,安全连接的成功率会更高,因为中介一般不会转换安全的数据流量。

以下代码段建立了一个 WebSocket 连接:

var host = "ws://example.microsoft.com";

var socket = new WebSocket(host);

ReadyState – Ready … Set … Go …

WebSocket.readyState 属性表示连接的状态:CONNECTING、OPEN、CLOSING 或 CLOSED。当首次创建 WebSocket 时,readyState 设置为 CONNECTING。建立连接后,readyState 设置为 OPEN。如果建立连接失败,则 readyState 将设置为 CLOSED。

注册 Open 事件

要在创建连接后接收通知,应用程序必须注册 Open 事件。

socket.onopen = function (openEvent) {

document.getElementById("serverStatus").innerHTML = 'Web Socket State::' + 'OPEN';

};

发送和接收消息的背景知识

在成功握手之后,应用程序和 Websocket 服务器便可进行 WebSocket 消息交换。消息由一个或多个消息片断或数据“帧”组成。

每一帧都包含特定信息,例如:

  • 帧长度
  • 消息类型(二进制或文本),位于消息的第一个帧中
  • 标记 (FIN),表明该帧是否为消息的最后一帧

图中演示了 WebSocket 帧的内容

IE10 将这些帧重新组合为完整的消息,然后将其传送至脚本。

发送和接收消息的编程

send API 允许应用程序以 UTF-8 文本、ArrayBuffer 或 Blob 的形式将消息发送至 Websocket 服务器。

例如,以下代码段将检索用户输入的文本并将其作为 UTF-8 文本消息发送至服务器以便进行回显。它将验证 Websocket 的 readyState 是否为 OPEN:

function sendTextMessage() {

if (socket.readyState != WebSocket.OPEN)

return;

 

var e = document.getElementById("textmessage");

socket.send(e.value);

}

以下代码段将检索用户在画布中绘制的图像并将其作为二进制消息发送至服务器:

function sendBinaryMessage() {

if (socket.readyState != WebSocket.OPEN)

return;

 

var sourceCanvas = document.getElementById('source');

// msToBlob returns a blob object from a canvas image or drawing

socket.send(sourceCanvas.msToBlob());

// ...

}

注册 Message 事件

要检索消息,应用程序必须注册 Message 事件。事件处理程序将接收 MessageEvent,在 MessageEvent.data 中包含有相关数据。所接收数据的格式可以是文本,也可以是二进制消息。

当接收到二进制消息时,WebSocket.binaryType 属性控制返回的消息是 Blob 还是 ArrayBuffer 数据类型。该属性可设置为“blob”或“arraybuffer”。

下面的示例使用了默认值“blob”。

该代码段从 Websocket 服务器接收回显图像或文本。如果数据为 Blob,则将返回图像并在目标画布中绘制该图像。否则,将返回 UTF-8 文本消息并在文本字段中进行显示。

socket.onmessage = function (messageEvent) {

if (messageEvent.data instanceof Blob) {

var destinationCanvas = document.getElementById('destination');

var destinationContext = destinationCanvas.getContext('2d');

var image = new Image();

image.onload = function () {

destinationContext.clearRect(0, 0, destinationCanvas.width, destinationCanvas.height);

destinationContext.drawImage(image, 0, 0);

}

image.src = URL.createObjectURL(messageEvent.data);

} else {

document.getElementById("textresponse").value = messageEvent.data;

}

};

关闭 WebSocket 连接的详细过程

与“打开”握手类似,也存在“关闭”握手。无论哪个端点(应用程序或服务器)都可以启动该握手。

将会向另一端点发送一种特殊的帧(关闭帧)。关闭帧中可以包含可选状态代码和关闭原因。协议中为状态代码定义了一组相应的。关闭帧的发送方不得在发送该帧之后再发送其他应用程序数据。

当另一个端点接收到关闭帧时,将会使用自己的关闭帧进行响应。在响应关闭帧之前,该端点可能会发送挂起消息。

图中演示了关闭帧消息和响应。

关闭 WebSocket 和注册 Close 事件的编程

该应用程序使用 close API 通过打开的连接来启动“关闭”握手:

socket.close(1000, "normal close");

要在关闭连接之后接收通知,应用程序必须注册 close 事件。

socket.onclose = function (closeEvent) {

document.getElementById("serverStatus").innerHTML = 'Web Socket State::' + 'CLOSED';

};

close API 接受两个可选参数:协议中定义的状态代码以及一条说明。状态代码必须为 1000 或介于 3000 和 4999 之间。当执行关闭时,readyState 属性将设置为 CLOSING。IE10 接收到来自服务器的关闭响应之后,readyState 属性将设置为 CLOSED 并触发 close 事件。

使用 Fiddler 查看 WebSocket 数据流量

Fiddler 是一种常用的 HTTP 调试代理。最新版本中提供了对 WebSocket 协议的一些相关支持。您可以检查 WebSocket 握手中交换的标头:

显示 WebSocket 请求的 Fiddler 屏幕截图。

还将在日志中记录所有 WebSocket 消息。在下面的屏幕截图中,您可以看到“spiral”已作为 UTF-8 文本消息发送至服务器并已回显:

显示 WebSocket 响应的 Fiddler 屏幕截图。

总结

如果您要了解有关 WebSocket 的详细信息,可以观看 2011 年 9 月 Microsoft //Build/ 大会的相关讨论:

如果您要了解如何使用 Microsoft 技术创建 WebSocket 服务,以下博文进行了全面的介绍:

我强烈建议您立即开始使用 WebSocket 进行开发,并向我们提供反馈意见和建议。

—Windows 网络高级项目经理 Brian Raymor