这篇文章简单总结了一下 Rails 中 ActionCable 的基本概念及实践。原文链接:https://step-by-step.tech/posts/basic-concept-and-usage-for-actionable
使用 Ruby On Rails 编写任何 Web 相关的应用程序都比想象中简单,其中也包括 Websocket 相关功能。为了提供 Websocket 服务,Rails 官方提供了ActionCable这一套工具集。能够快速实现 Pub/Sub 模型。
ActionCable 其实是对 Websocket 所做的 Ruby 封装,让我们能够很方便地去实现 Websocket 相关的功能。然而 ActionCable 本身包含了不少概念
官方文档对上面的概念有十分详尽的描述,这里就不赘述了,我就用个比较形象的例子来说明。可以想象一下我们平时使用微信进行聊天的过程。
当我们打开微信的时候实际上就相当于作为一个 Consumer(消费者)创建了个人与微信服务器之间的一个 Connection(连接)。接下来我们通过微信群跟其他人进行聊天,则相当于我们订阅了该微信群,微信群则相当于 Channel(渠道),而我们则是这个渠道的 Subscriber(订阅者)。当群里有人说话的时候,微信服务器则向所有订阅了该渠道的组群成员推送信息,这个过程称之为 Broadcasting(广播),然后所有在群里的人都能够收到信息。我们跟微信群的关系可以理解成 Pub/Sub(发布 - 订阅模型)。
另一个值得提一下的就是,我们只需要打开一次微信,就可以参加多个微信群的群聊。相当于我们作为一个消费者,只需要创建一个连接,却可以订阅多个渠道,接收来自不同渠道的广播,同时我们也可以将自己要说的话广播给其他人。
这样一类比会不会对 ActionCable 的运作模式有了一个初步的了解了呢?
根据上面描述的基本概念,假设我们需要为客户端提供 Websocket 服务,那么我们需要向用户提供的东西主要有
这两者在 ActionCable 中实现可以说超级简单。
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
# 连接创建的时候会调用
def connect
# connect
end
end
end
这个方法在建立 Websocket 连接的时候会调用,从前面跟微信的类比可以看出,Websocket 服务自始自终只需要建立一次连接(不包括断开重连)。却可以通过订阅不同的渠道来得到来自服务端的不同推送。接下来看看渠道的实现。
在 ActionCable 里面,不同的渠道都继承自ApplicationCable::Channel
。假设我们网站首页需要接收来自服务端的推送,那么渠道可以简单地这样去实现
# app/channels/home_channel.rb
class HomeChannel < ApplicationCable::Channel
def subscribed
stream_from 'home_page'
end
end
建立连接(客户端代码后面再提供)并订阅HomeChannel
这个渠道之后,客户端便可获取来自服务端的推送。当服务端需要往客户端推送数据的时候可以
ActionCable.server.broadcast('home_page', {
action: 'create_tender',
price: 1000
})
在HomeChannel#subscribed
中已经定义好,客户端会接收来自于拥有home_page
这个标识的渠道的相关推送。数据集可以自定义(这个可以事先沟通好)。
前面采用stream_from
方法指定了home_page
标识的推送,这种模式常用于较为通用的信息推送。比方说,网站就只有一个首页,所有人的首页都统一订阅HomeChannel
这个渠道,并得到相同的推送信息。这跟前面的微信群聊稍微有些不同,因为不同的聊天室推送的内容肯定有所区别,我们又不可能为每一个微信群都创建一个xxxChannel
的渠道文件来对应不同的微信群,这样就有成千上万个 Channel 文件需要维护了。我们拿常见的直播间举例,当用户加入不同的直播间会以直播间的唯一标识来订阅该直播间渠道,并获得来自该直播间的推送。那么服务端代码大概是
# app/channels/studio_channel.rb
class StudioChannel < ApplicationCable::Channel
def subscribed
studio = Studio.find(params[:id])
stream_for studio
end
end
接下来我们就可以通过不同的直播间 id,来订阅指定的直播间渠道,以实现不同的直播间可以推送不一样的内容。推送代码如下
> @studio = Studio.find(1)
> StudioChannel.broadcast_to(@studio, {
action: 'join'
users_count: @studio.users_count,
user_name: 'Ruby'
})
这条信息只往id=1
的直播间推送,推送的数据可以自由定制,跟前端协调好即可,其他的直播间不会受到影响。
从上面可以了解到 ActionCable 的服务端基本用法,简单概括就是
接下来看看客户端要如何创建连接并订阅渠道。
谈了服务端的用法,接下来谈谈客户端该如何跟服务端交互。如果只是在浏览器上面使用的 Web 页面,其实使用官方的工具包是最简单的。而如果客户端是小程序/App,那么可能要自己“手写 SDK”。需要窥探一下源码,其实也不难。
先讲讲如何用官方的工具包来实现跟 ActionCable 服务端交互。
import { createConsumer } from "@rails/actioncable"
const customer = createConsumer('wss://www.example.com/cable') // ActionCable默认连接路径都是/cable。
// 订阅id=1的直播间
const channel = consumer.subscriptions.create({ channel: "StudioChannel", id: 1 }, {
// 接收推送信息
received(data) {
// 服务端运行`StudioChannel.broadcast_to(@studio, { hello: 1 })`后,这里会打印出 { hello: 1 }
console.log(data)
}
})
前面保存了channel
这个变量,我们可以在程序的某处取消对该渠道的订阅
channel.unsubscribe();
从上面的例子可以看出,采用官方的工具包完成对 ActionCable 渠道的订阅并获取推送信息是如此的简单。然而在很多场景下,其实我们并没有办法直接使用官方工具包(比方说开发小程序/App 的时候)。在这种场景下我们只能自行研发工具包了。窥探一下官方源代码其实很容易写出来,这里只提供 JavaScript 的代码,其他语言大同小异。
window.websocket = new WebSocket('wss://www.example.com/cable')
// 唯一标识
const identifier = JSON.stringify({
channel: 'StudioChannel',
id: 1
})
// 接收推送信息
websocket.onmessage = function(data) {
console.log(data)
}
websocket.onopen = function(data) {
// 订阅
window.subscription = websocket.send(JSON.stringify({
command: 'subscribe',
identifier
}))
}
// 取消订阅
websocket.send(JSON.stringify({
command: 'unsubscribe',
identifier
}))
代码还算比较简单,简单概括就是把参数组装成唯一标识,进行订阅并设置消息接收回调,在某些场景下还能取消对该渠道的订阅。
PS: WebSocket 是单一连接 + 多渠道订阅的,在一些场景下容易断连。需要根据实际场景实现重连逻辑。否则断连后客户端将收不到任何消息推送。
这篇文章简单总结了一下 Rails 中 ActionCable 的基本概念及实践。其中包括如何用 ActionCable 开发出一个简单的 Websocket 服务,并推送消息。另外,客户端也能够使用恰当的方式连接该服务,官方提供了简易的工具包可以实现这一点,然而这个工具包对于很多场景(小程序/App)并不是很适用,于笔者窥探了一下工具包的源码,并提供了一个暴露原理的 JavaScript 版本,其他语言也可以参考这个范例,以连接基于 ActionCable 的 Websocket 服务。