WebSockets 简介 - 将套接字引入网络

问题:低延迟的客户端-服务器及服务器-客户端连接

一直以来,网络在很大程度上都是围绕着所谓的 HTTP 请求/响应范式构建的。客户端加载网页,然后直到用户点击下一页之前什么都不会发生。大约在 2005 年,AJAX 开始让网络变得更加动态了。尽管如此,所有 HTTP 通信仍由客户端掌控,这需要用户互动或定期轮询,才能从服务器加载新数据。

早已存在让服务器知道有新数据可用时,立即将数据发送到客户端的技术。此类应用由“Push”或 Comet 等名称组成。“长轮询”是创建服务器发起连接的假象的最常见黑客手段。如果使用长轮询,客户端会打开与服务器的 HTTP 连接,该连接会一直保持打开状态,直到发送响应。只要服务器确实有新数据,就会发送响应(其他技术涉及 FlashXHR 多部分请求,也称为 htmlfile)。 长轮询和其他技术都很有效。您经常在 Gmail 聊天等应用程序中使用它们。

然而,所有这些解决方法都存在一个问题:它们带有 HTTP 的开销,因此它们并不适合低延迟应用。想想浏览器上的多人第一人称射击游戏,或任何其他带有即时元素的在线游戏。

WebSocket 简介:将套接字引入 Web

WebSocket 规范定义了一种 API,用于在网络浏览器和服务器之间建立“套接字”连接。简单地说:客户端和服务器之间存在持久连接,双方都可以随时开始发送数据。

使用入门

只需调用 WebSocket 构造函数即可打开 WebSocket 连接:

var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);

请注意 ws:。这是 WebSocket 连接的新网址架构。此外,还有用于安全 WebSocket 连接的 wss:,与用于安全 HTTP 连接的 https: 相同。

通过立即向连接附加一些事件处理脚本,可以了解连接打开、收到传入消息或出现错误的时间。

第二个参数接受可选的子协议。它可以是字符串或字符串数组。每个字符串都应代表一个子协议名称,而服务器仅接受数组中传递的子协议之一。可通过访问 WebSocket 对象的 protocol 属性来确定接受的子协议。

子协议名称必须是在 IANA 注册表中注册的子协议名称之一。截至 2012 年 2 月,目前只有一个注册子协议名称 (soap)。

// When the connection is open, send some data to the server
connection.onopen = function () {
connection.send('Ping'); // Send the message 'Ping' to the server
};

// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};

// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};

与服务器通信

与服务器建立连接后(触发 open 事件时),我们可以开始对连接对象使用 send('your message') 方法,将数据发送到服务器。它过去仅支持字符串,但在最新规范中,它现在也可以发送二进制消息。如需发送二进制数据,您可以使用 BlobArrayBuffer 对象。

// Sending String
connection.send('your message');

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
connection.send(binary.buffer);

// Sending file as Blob
var file = document.querySelector('input[type="file"]').files[0];
connection.send(file);

同样,服务器可能随时向我们发送消息。只要发生这种情况,就会触发 onmessage 回调。该回调会接收一个事件对象,并且可以通过 data 属性访问实际消息。

WebSocket 也可以接收采用最新规范的二进制消息。二进制帧可以采用 BlobArrayBuffer 格式接收。要指定收到的二进制数据的格式,请将 WebSocket 对象的 binaryType 属性设为“blob”或“arraybuffer”。默认格式为“blob”。(您无需在发送时对齐 binaryType 参数)。

// Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'
connection.binaryType = 'arraybuffer';
connection.onmessage = function(e) {
console.log(e.data.byteLength); // ArrayBuffer object if binary
};

WebSocket 的另一个新增功能是扩展。使用扩展,可以发送压缩帧、多路复用帧等。您可以检查 open 事件后的 WebSocket 对象的 extensions 属性,找到服务器接受的扩展。截至 2012 年 2 月,还没有正式发布的扩展规范。

// Determining accepted extensions
console.log(connection.extensions);

跨域通信

作为一种现代协议,跨源通信已内置在 WebSocket 中。尽管您仍应确保只与您信任的客户端和服务器通信,但 WebSocket 可实现任何域上各方之间的通信。服务器会决定是将其服务提供给所有客户端,还是仅向位于一组明确定义的域上的客户端提供。

代理服务器

每一种新技术,都会伴随着一系列问题。WebSocket 也兼容大多数公司网络中用于调节 HTTP 连接的代理服务器。WebSocket 协议使用 HTTP 升级系统(通常用于 HTTP/SSL)将 HTTP 连接“升级”为 WebSocket 连接。某些代理服务器不支持这种操作,因此会断开连接。因此,即使指定的客户端使用了 WebSocket 协议,也可能无法建立连接。因此,下一部分内容变得更加重要 :)

立即使用 WebSocket

WebSocket 仍是一项新兴技术,并未在所有浏览器中全面实现。不过,当 WebSocket 不可用时,您可以通过使用上述某种回退方案的库使用 WebSocket。socket.io 在此领域已经非常流行,它附带了协议的客户端和服务器实现,并包含回退库(从 2012 年 2 月开始,socket.io 尚不支持二进制消息传递)。此外,还有一些商业解决方案,如 PusherApp,可通过提供用于向客户端发送 WebSocket 消息的 HTTP API,轻松集成到任何网络环境中。由于额外的 HTTP 请求,与纯 WebSocket 相比,始终会产生额外的开销。

服务器端

使用 WebSocket 为服务器端应用带来了全新的使用模式。虽然 LAMP 等传统服务器堆栈是围绕 HTTP 请求/响应循环而设计的,但通常无法很好地处理大量打开的 WebSocket 连接。要同时保持大量连接处于打开状态,需要一种能以低性能成本接收高并发数据的架构。此类架构通常围绕线程处理或所谓的非阻塞 IO 设计。

服务器端实现

协议版本

WebSocket 的有线协议(客户端与服务器之间的握手和数据传输)现为 RFC6455。最新的 Chrome 浏览器和 Android 版 Chrome 浏览器与 RFC6455 完全兼容,包括二进制消息传递。另外,Firefox 11 和 Internet Explorer 10 也会兼容。您仍然可以使用旧版协议,但由于已知存在漏洞,我们不建议您使用该版本。如果您有旧版 WebSocket 协议的服务器实现,我们建议您将其升级到最新版本。

使用场景

如果您需要在客户端和服务器之间建立真正低延迟、近乎实时的连接,则可以使用 WebSocket。请注意,这可能需要您重新考虑构建服务器端应用的方式,将新的关注点放在事件队列等技术上。以下是一些示例用例:

  • 多人在线游戏
  • 聊天应用
  • 体育赛事直播
  • 实时更新社交视频流

样本歌曲

参考