问题:低延迟客户端-服务器和服务器-客户端连接
网络在很大程度上是围绕 HTTP 的请求/响应模式构建的。客户端加载一个网页,然后直到用户点击下一页之前,什么都不会发生。2005 年左右,AJAX 开始让网络变得更加动态。尽管如此,所有 HTTP 通信还是由客户端控制,这需要用户互动或定期轮询,以便从服务器加载新数据。
长期以来存在着各种技术,可让服务器得知有新数据可用时,立即将数据发送到客户端。这些符号的名称为“推送”或“Comet”。产生由服务器发起的连接这种错觉的最常见黑客行为之一是长轮询。通过长轮询,客户端会打开与服务器的 HTTP 连接,而服务器会一直保持连接打开,直到发送响应。每当服务器确实有新数据时,它就会发送响应(其他技术包括 Flash、XHR 多部分请求以及所谓的 htmlfile)。 长轮询和其他技术都非常实用。您每天在 Gmail 聊天等应用程序中使用它们。
但是,这些解决方案都存在一个共同问题:它们带有 HTTP 的开销,因此不适合低延迟应用。比如浏览器中的多人第一人称射击游戏,或任何其他带有实时组件的在线游戏。
WebSocket 简介:将套接字引入 Web
WebSocket 规范定义了一个在网络浏览器与服务器之间建立“套接字”连接的 API。简而言之,客户端与服务器之间存在持久连接,且双方可以随时开始发送数据。
入门指南
只需调用 WebSocket 构造函数即可打开 WebSocket 连接:
var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);
请注意 ws:
。这是 WebSocket 连接的新网址架构。安全 WebSocket 连接也有 wss:
,与 https:
用于安全 HTTP 连接的方式相同。
立即将一些事件处理脚本附加到连接后,您就可以了解连接打开、收到传入邮件或发生错误的时间。
第二个参数可接受可选的子协议。它可以是一个字符串,也可以是字符串数组。每个字符串都应代表一个子协议名称,而服务器仅接受数组中已通过的一个子协议。访问 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')
方法向服务器发送数据了。它过去仅支持字符串,但在最新的规范中,它现在也可以发送二进制消息。如需发送二进制数据,您可以使用 Blob
或 ArrayBuffer
对象。
// 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 还可以接收最新规范的二进制消息。可以采用 Blob
或 ArrayBuffer
格式接收二进制帧。要指定收到的二进制数据的格式,请将 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,可以通过提供 HTTP API 来向客户端发送 WebSocket 消息,轻松集成到任何网络环境中。由于额外的 HTTP 请求,与纯 WebSocket 相比,始终会有额外的开销。
服务器端
使用 WebSocket 为服务器端应用带来了全新的使用模式。虽然 LAMP 等传统服务器堆栈是围绕 HTTP 请求/响应周期而设计的,但它们通常无法很好地处理大量打开的 WebSocket 连接。若要同时保持大量连接处于打开状态,需要一种能以较低的性能成本接收高并发操作的架构。此类架构通常围绕线程处理或所谓的非阻塞 IO 而设计。
服务器端实现
- Node.js
- Java
- Ruby
- Python
- Erlang
- C++
- .NET
协议版本
WebSocket 的传输协议(客户端与服务器之间的握手和数据传输)现在为 RFC6455。最新版的 Chrome 和 Android 版 Chrome 与 RFC6455 完全兼容,包括二进制消息传递。另外,Firefox 11 和 Internet Explorer 10 也会兼容。您仍然可以使用旧版协议,但不建议使用,因为这些版本已知存在漏洞。如果您有旧版 WebSocket 协议的服务器实现,我们建议您将其升级到最新版本。
用例
如果您需要在客户端与服务器之间建立极低延迟、近乎实时的连接,请使用 WebSocket。请记住,这可能涉及重新考虑构建服务器端应用的方式,并将新的关注点放在事件队列等技术上。以下是一些用例示例:
- 多人在线游戏
- 聊天应用
- 体育赛事直播
- 实时更新社交信息流