问题:低延迟客户端-服务器和服务器-客户端连接
网络在很大程度上是围绕所谓的 HTTP 请求/响应范式构建的。客户端加载网页,然后什么也不会发生,直到用户点击进入下一个网页。2005 年前后,AJAX 开始让网络变得更具动态性。不过,所有 HTTP 通信都由客户端控制,需要用户互动或定期轮询才能从服务器加载新数据。
让服务器在得知有新数据可用时立即将数据发送到客户端的技术已经存在了相当长的时间。它们被称为“推送”或“Comet”。最常见的黑客手段之一是长轮询,它会制造一种由服务器发起连接的假象。使用长轮询时,客户端会打开与服务器的 HTTP 连接,服务器会保持该连接处于打开状态,直到发送响应。每当服务器实际有新数据时,它都会发送响应(其他技术涉及 Flash、XHR 多部分请求和所谓的 htmlfiles)。 长轮询和其他技术的效果非常好。您每天都会在 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 的另一项新增功能是扩展。使用扩展程序,可以发送压缩、多路复用等帧。您可以在打开事件后通过检查 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 消息,因此可以轻松集成到任何 Web 环境中。由于存在额外的 HTTP 请求,因此与纯 WebSocket 相比,始终会有额外的开销。
服务器端
使用 WebSocket 为服务器端应用创建了一种全新的使用模式。虽然 LAMP 等传统服务器堆栈是围绕 HTTP 请求/响应周期设计的,但它们通常无法很好地处理大量打开的 WebSocket 连接。同时保持大量连接打开需要一种能够以较低的性能成本接收高并发的架构。此类架构通常围绕线程或所谓的非阻塞 IO 进行设计。
服务器端实现
- Node.js
- Java
- Ruby
- Python
- Erlang
- C++
- .NET
协议版本
WebSocket 的有线协议(客户端和服务器之间的握手和数据传输)现在是 RFC6455。最新版 Chrome 和 Chrome(Android 版)完全兼容 RFC6455,包括二进制消息传递。此外,Firefox 11 和 Internet Explorer 10 也将兼容。您仍然可以使用旧版协议,但不建议这样做,因为这些协议存在已知漏洞。如果您有旧版 WebSocket 协议的服务器实现,我们建议您将其升级到最新版本。
使用场景
如果您需要在客户端和服务器之间建立真正的低延迟、近乎实时的连接,请使用 WebSocket。请注意,这可能需要您重新思考如何构建服务器端应用,并重点关注事件队列等技术。一些示例应用场景包括:
- 多人在线游戏
- Chat 应用
- 体育赛事直播滚动条
- 实时更新社交媒体动态