試用 Action Cable

Action Cable 有什么用

Action Cable 是一項滿足客戶端與服務器端實時通訊需求的功能,它基于 WebSocket 協議。在此之前 web 端要滿足類似的需求,有 輪詢、長輪詢、SSE(Server Sent Events ,sinatra 自帶一個簡單的實現,有興趣可以看看) 等方法,綜合考慮開銷和兼容性,基于 WebSocket 的實現是最好的。

WebSocket 的基本知識

websocket 是建立在 TCP 協議上面應用層的協議,整個協議由兩部分組成: 握手建立連接,數據傳輸。要建立 websocket 連接,得先由客戶端發送一個 http get 請求,帶上相關的請求頭,只有當服務器端帶上正確的響應頭回復時,連接才能建立。之后客戶端和服務器端可以向對方發送數據。

更詳細的說明,可以看這里

如果想簡單地動手玩玩 websocket ,請參考這篇文章

Action Cable 基礎概念

用戶打開的每個瀏覽器標簽頁都會跟服務器建立一條新連接(connection),Rails 會為這條連接實例化一個 connection 對象,這個對象負責管理此后發生的訂閱事件,它不處理具體的業務邏輯。

客戶端可以通過一個連接訂閱多個頻道(channel)。每個頻道都提供多個 websocket 的回調方法,方便寫業務邏輯代碼。

一個頻道可以包含一個或多個流(stream),如果把頻道比作網絡游戲平臺中的某個分區,那么流就是分區下面的某個房間。流是 Action Cable 中發送、接收消息的最小單位。

服務器可以在建立連接時設置驗證(用異步的方式),一旦驗證失敗,將關閉已建立的連接。

整個 Action Cable 的架構粗略看起來就是下面的樣子:

connections  <==   channels  <==   streams <--> subscriptions  ==>   connections

<== 表示 一 對 多 的關系

<--> 表示 一對一 的關系

==> 表示 多對一 的關系

Action Cable 基本配置

以下代碼、說明僅在 Rails 5.0.0 版本(不是 beta 版本)測試過,不同版本間 Action Cable 的表現有稍許區別。

既然 websocket 需要由客戶端發起(握手請求),先從前端需要做的事情說起。

運行 rails new my_actioncable 新建一個 Rails 項目。

app/assets/javascripts/cable.js ,Rails 已經替你準備好前端的 connection 實例:

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();

}).call(this);

為方便調試,你可以添加一行啟用調試的代碼:

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();
  ActionCable.startDebugging(); // 啟用調試

}).call(this);

連接創建好之后,接著創建一個訂閱。在 assets/javascripts/channels 目錄下新建 room.js 文件,內容如下:

App.room = App.cable.subscriptions.create("RoomChannel", {
  connected: function(){
    // Called when the subscription is ready for use on the server
    },
  disconnected: function() {
    // Called when the subscription has been terminated by the server
  },

  received: function(data) {
    // Called when there's incoming data on the websocket for this channel
  }
});

create 方法第一個參數可以是字符串,也可以是對象;如果是字符串,它表示要訂閱的頻道,如果是對象,則一定要帶有 key 為 channel 的字段,其他字段可以傳給后臺別作它用(比如創建流),如:

{
  channel: 'RoomChannel',
  label: '1st'
}

create 方法第二個參數包含一系列的回調方法,各自用途注釋都寫得很清楚。

前端的事情就做完了,開始設置后臺。

Action Cable 可以獨立于我們的應用運行,也可以作為引擎掛載到我們的應用中。這里我們選擇掛載。

routes.rb 添加一行:

mount ActionCable.server => '/cable'

這樣發向 '/cable' 的請求將由 Action Cable 處理。

app/channels 目錄下新建 room_channel.rb ,內容如下:

class RoomChannel < ApplicationCable::Channel
  def subscribed
    stream_from 'room_channel'
  end
  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end

steam_from 新建一個流,如果前端在調用 App.cable.subscriptions.create 時第一個參數是對象,可以通過 params 來獲取對象的內容:

# 假設 第一個參數是對象
# {
#   channel: 'RoomChannel',
#   label: '1st'
# }

def subscribed
  stream_from "room:#{params[:label]}"
end

運行 rails g controller room show 生成控制器、路由和一個簡單的 show 頁面。

現在前后臺都搭好了,得到一個最簡單,什么都不能做的 Action Cable 。運行 rails s 啟動服務器端,打開瀏覽器訪問 localhost:3000/room/show

在瀏覽器的控制臺可以看到類似以下的信息:

[ActionCable] Opening WebSocket, current state is null, subprotocols: actioncable-v1-json,actioncable-unsupported 1470455374056 action_cable.self-1641ec3….js?body=1:50 
[ActionCable] ConnectionMonitor started. pollInterval = 3000 ms 1470455374063 action_cable.self-1641ec3….js?body=1:50 
[ActionCable] WebSocket onopen event, using 'actioncable-v1-json' subprotocol 1470455374081 action_cable.self-1641ec3….js?body=1:50 
[ActionCable] ConnectionMonitor recorded connect 1470455374082

切換到控制臺的 Network 標簽,查看 WebSockets ,可以看到瀏覽器每隔 3 秒會收到服務器端發過來的 ping 包。

服務器主動向客戶端推送消息

服務器可以主動通過廣播向客戶端推送消息。

為免阻塞正常的 http 響應,通常會采用 delayed job 來向客戶端推送消息。

運行命令 rails g job send_msg 新建一個 delayed job ,在新建的 send_room_msg_job.rb 中的 perform 方法中添加:

# 每 3 秒向客戶端發送一條信息
1.upto(10) do |i|
  sleep 3
  ActionCable.server.broadcast(
      'room_channel', # 這是流的名字,要跟在 stream_from 定義的保持一致
      title: 'the title',
      body: "server send #{i}"
  )
end

然后在 RoomController#show 方法中添加:

SendRoomMsgJob.perform_later

客戶端接收部分,重寫 assets/javascripts/channels/room.jsreceived 回調方法:

//...

received: function(data) {
  var msg = data['title'] + '\n' + data['body'] + '\n';
  //簡單地打印接收到的信息
  console.log(msg);
}

重啟服務器、重新訪問 localhost:3000/room/show ,每隔 3 秒就能看到打印信息。

客戶端主動向服務器發送消息

客戶端也可以主動調用服務器端在 channel 中定義的方法。

重寫 assets/javascripts/channels/room.jsconnected 回調方法:

//...

connected: function(){
  this.perform('print_log', { msg: 'send from client' });
}

room_channel.rb 中添加一個 print_log 方法:

#...

def print_log(data)
  p ">>>> #{data['msg']}"
end

只要連接一建立,就可以在服務器后臺看到打印 >>>> message from client

以上就是簡單的 Action Cable 試用記錄,源碼已經上傳至 github

參考文章:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容