如何使用 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://#/chat');
chat.on('connect', function () {
chat.emit('hi!');
});
var news = io.connect('https://#/news');
news.on('news', function () {
news.emit('woot');
});
我认为这个 API 相当令人困惑 - 在底层,Socket.io 只打开一个连接,但阅读代码会给我们一个不同的故事。
SockJS 的方式
与 Socket.io 不同,SockJS 没有任何神奇的 API。 它看起来像一个 WebSocket 对象,它的行为也像一个。 没什么令人惊讶的。
那么如何解决多路复用问题呢?
如果可能,通常最好避免发明新的 API,而是使用已有的 API。 为什么不将每个多路复用通道都表示为一个 WebSocket 对象呢?
我建议的方法很简单 - 您获取一个真实的 SockJS(或 WebSocket)连接,将其包装在多路复用层中,并从中提取任意数量的伪 WebSocket 对象。 它们将在内部进行多路复用,但从模块的角度来看 - 这将是完全透明的。 就模块而言,它与 WebSocket 对象对话。
就是这样。 这有点像魔术师的帽子。 您放入一个 WebSocket 连接,就可以取出任意数量的伪 WebSocket 连接。
这种方法比 Socket.io 提出的方法更好 - 您可以创建仅依赖于原生 WebSocket API 的代码。 稍后,当需要时,您可以只传递一个伪 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” node 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 创建可组合代码真正需要的一切!