跳至主内容

如何使用 WebSockets 组合应用程序

·阅读时长4分钟
Marek Majkowski

或者:如何在 WebSockets 或 SockJS 上正确实现多路复用

正如您可能知道的,WebSockets 是一项很棒的新 HTML5 技术,它允许您异步发送和接收消息。我们的兼容性层 - SockJS - 模拟了它,即使在旧浏览器或代理后面也能正常工作。WebSockets 在概念上非常简单。API 主要包括:连接、发送和接收。但是,如果您的 Web 应用程序有许多模块,并且每个模块都想要发送和接收数据,该怎么办?

理论上,你可以为每个模块打开多个 WebSocket 连接。虽然这种方法不是最优的(因为需要处理多个 TCP/IP 连接),但对于原生 WebSocket 来说是可行的。然而遗憾的是,由于 HTTP 的技术限制,SockJS 无法实现这一点:对于某些回退传输机制,无法同时向同一台服务器打开多个连接。这个问题是真实存在的,并且值得解决。让我换一种说法。

假设你只能与给定的主机保持单个连接,但有多个模块需要发送和接收数据,你该怎么办?

你需要多路复用(Multiplexing):将来自多个源的数据组合到一个连接中。接下来的问题是使用什么 API;如何在代码中实现多路复用?

Socket.io 的方式

Socket.io 提供了一种试图解决此问题的 API,它称之为“命名空间(namespaces)”。以下是浏览器客户端代码示例

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 对象,表现也像一个 WebSocket 对象。没什么令人惊讶的。

那么如何解决多路复用问题呢?

如果可能的话,尽量避免发明新的 API,而应使用现有的标准 API,这通常是个好主意。为什么不把每个多路复用的通道都呈现为一个 WebSocket 对象呢?

我的建议非常简单:你获取一个真实的 SockJS(或 WebSocket)连接,将其封装在一个多路复用层中,然后从中提取出任意数量的“伪”WebSocket 对象。它们在内部是多路复用的,但从模块的角度来看,这一切是完全透明的。对该模块而言,它就是在和一个标准的 WebSocket 对象对话。

就是这样。这有点像魔术师的帽子:放入一个 WebSocket 连接,就可以取出任意数量的“伪”WebSocket 连接。

这种方法比 Socket.io 提出的方案更好——你可以编写仅依赖于原生 WebSocket API 的代码。稍后,当需要时,你可以直接传入一个“伪”WebSocket 对象来代替真实的 WebSocket 对象。换句话说:它支持组合(compose)。问题解决了。

实现

如果之前在浏览器中你使用单个 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 实例的代码即可。这就是你使用 WebSocket 创建可组合代码所需要的全部!

© . This site is unofficial and not affiliated with VMware.