如何使用 WebSockets 构建应用程序
或者:如何在 WebSockets 或 SockJS 上正确进行多路复用
如您所知,WebSockets 是一种很酷的 HTML5 新技术,它允许您异步发送和接收消息。我们的兼容层 - SockJS - 模拟它,即使在旧浏览器或代理后面也能工作。WebSockets 在概念上非常简单。API 基本上是:连接、发送和接收。但是,如果您的 Web 应用程序有多个模块,每个模块都需要能够发送和接收数据,该怎么办呢?
理论上,您可以打开多个 WebSocket 连接,每个模块一个。虽然这种方法并非最佳(因为需要处理多个 TCP/IP 连接),但它适用于原生 WebSockets。但是,不幸的是,由于 HTTP 的技术限制,它不适用于 SockJS:对于某些回退传输,一次无法对单个服务器打开多个连接。这个问题是真实存在的,值得解决。让我重新解释一下。
假设您只能与给定主机建立单个连接,而多个模块需要发送和接收数据,您该怎么办呢?
您需要 多路复用:将来自多个源的数据合并到单个连接中。下一个问题是您使用什么 API;您如何在代码中暴露多路复用?
Socket.io 的方法
Socket.io 有一个 API 试图解决这个问题,它称之为“命名空间”。以下是一些示例客户端(浏览器)代码
var chat = io.connect('https://127.0.0.1/chat');
chat.on('connect', function () {
chat.emit('hi!');
});
var news = io.connect('https://127.0.0.1/news');
news.on('news', function () {
news.emit('woot');
});
我认为这个 API 非常令人困惑 - 在幕后,Socket.io 只是打开了一个连接,但读取代码却给了我们不同的故事。
SockJS 的方法
与 Socket.io 相反,SockJS 没有任何神奇的 API。它看起来像一个 WebSocket 对象,它的行为也像一个 WebSocket 对象。没什么好奇怪的。
那么如何解决多路复用问题呢?
如果可能,最好避免发明新的 API,而是使用已经建立的 API。为什么不将每个多路复用通道都呈现为一个 WebSocket 对象呢?
我的建议很简单 - 您将一个真正的 SockJS(或 WebSocket)连接包装在一个多路复用层中,然后从中提取任意数量的伪 WebSocket 对象。它们将在内部进行多路复用,但从模块的角度来看 - 这将完全透明。模块会与 WebSocket 对象进行通信,因为它认为该对象就是 WebSocket 对象。
就是这样。这有点像魔术师的帽子。您放入一个 WebSocket 连接,就可以取出任意数量的伪 WebSocket 连接。
这种方法比 Socket.io 提出的方法更好 - 您可以创建只依赖于原生 WebSocket API 的代码。稍后,当需要时,您可以只传递伪 WebSocket 对象,而不是真正的 WebSocket 对象。换句话说:它可以组合。问题解决。
实现
如果您之前在浏览器中使用的是单个 SockJS 连接,就像这样
var sockjs = new SockJS('/echo');
您可以修改客户端代码为
var real_sockjs = new SockJS('/echo');
var multiplexer = new WebSocketMultiplex(real_sockjs);
var fake_sockjs_1 = multiplexer.channel('ann');
var fake_sockjs_2 = multiplexer.channel('bob');
此时,这些“伪”对象的行为将与正常的 SockJS 对象完全相同。您可以预期听到“open”、“message”和“close”事件。(底层代码大约有 60 行 javascript)同样,服务器端 - 它通常使用常用的“net.Server”和“Stream”节点 API
var service = sockjs.createServer();
更改后
var real_service = sockjs.createServer();
var multiplexer = new multiplex_server.MultiplexServer(real_service);
var fake_service_1 = multiplexer.registerChannel('ann');
var fake_service_2 = multiplexer.registerChannel('bob');
同样,“伪”对象将执行通常的操作,当用户订阅了该特定通道时,它们将发出“connected”事件。(底层多路复用器代码 也不太复杂)
如果您想查看多路复用器代码的实际效果
最后的想法
值得强调的是,这种方法确实可以组合。任何模块都可以获取一个伪 WebSocket 对象,并重复该技巧以获取更多第二层伪 WebSocket 对象。与其发明新的 API,不如只创建依赖于传递给构造函数的 WebSocket 实例的代码。这将是您使用 WebSockets 创建可组合代码所需的一切!