1. 介绍

    websocket的序列文章重点要讲的就是actioncable,之前也讲了好多关于各种方式实现聊天室的文章,相信从中,也能学到好多关于websocket实践的知识和经验,这节要来讲讲actioncable。
    actioncable是集成在rails 5中的一个功能,它能够轻易的在rails中使用websocket。现在先把actioncable用起来,再慢慢研究其原理和特性。

    1. 使用

    还是跟先前的例子一样,建立一个聊天室。
    2.1 聊天室界面

    首先,rails的版本必须得是5以上,写这篇文章的时候,rails 5正式版还没有出来,目前的版本是5.0.0.beta4。

    1. $ rails new actioncable_demo
    2. $ cd actioncable_demo

    这样就生成了一个新项目。
    接着创建message这个model,存储聊天记录。

    1. $ rails g model message content:text
    2. $ rails db:migrate

    创建聊天室的界面。
    在config/routes.rb中添加路由。

    1. Rails.application.routes.draw do
    2. get 'rooms/show'
    3. end

    创建controller,添加app/controllers/rooms_controller.rb文件,内容如下:

    1. class RoomsController < ApplicationController
    2. def show
    3. @messages = Message.all
    4. end
    5. end

    添加view,添加app/views/rooms/show.html.erb文件,内容如下:

    1. <h1>Chat room</h1>
    2. <div id="messages">
    3. <%= render @messages %>
    4. </div>
    5. <form>
    6. <label>Say something:</label><br>
    7. <input type="text" data-behavior="room_speaker">
    8. </form>

    还有app/views/messages/_message.html.erb文件,内容如下:

    1. <div class=“message”>
    2. <p><%= message.content %></p>
    3. </div>

    到目前为止,按照之前的经验,界面都建立好了,如下图所示:
    8、actioncable 入门 - 图1
    2.2 开启websocket

    接下来,就是要来处理websocket部分。
    先在客户端浏览器中开启websocket请求。
    actioncable默认提供了一个文件app/assets/javascripts/cable.coffee,把几行注释打开,就可以开启websocket,内容如下:

    1. #
    2. #= require action_cable
    3. #= require_self
    4. #= require_tree ./channels
    5. #
    6. @App ||= {}
    7. App.cable = ActionCable.createConsumer()

    其实这些js的内容很简单,它做的主要的事情就是前面几篇文章所讲的在客户端浏览器执行new WebSocket,具体的内容可以查看其源码。
    还要在路由中添加下面这行,把websocket服务以engine的方式挂载起来。

    1. mount ActionCable.server => '/cable'

    至此,websocket已经开启了,可以通过chrome浏览器的开发者工具查看链接的信息,只要有101协议的信息,表示就是成功的。
    8、actioncable 入门 - 图2
    2.3 channel

    现在要让客户端和服务器端连接起来。
    actioncable提供了一个叫做channel的技术,中文名可以称为”通道”。actioncable是一种pub/sub的架构,服务器通过channel发布消息,多个客户端通过对应的channel订阅消息,服务器能够广播消息给客户端,从而实现客户端和服务器端的交互。
    先新建一个channel。

    1. $ rails g channel room speak
    2. 修改app/channels/room_channel.rb文件,内容如下:
    3. class RoomChannel < ApplicationCable::Channel
    4. def subscribed
    5. stream_from "room_channel"
    6. end
    7. def unsubscribed
    8. # Any cleanup needed when channel is unsubscribed
    9. end
    10. def speak(data)
    11. # ActionCable.server.broadcast "room_channel", message: data['message']
    12. Message.create! content: data['message']
    13. end
    14. end

    其中定义了三个方法,分别是subscribed,unsubscribed,speak。
    subscribed和unsubscribed方法是默认就生成的,而speak是我们自己定义的。
    subscribed表示的是当客户端连接上来的时候使用的方法。
    unsubscribed表示的是当客户端与服务器失去连接的时候使用的方法。
    还有,app/assets/javascripts/channels/room.coffee文件,内容如下:

    1. App.room = App.cable.subscriptions.create "RoomChannel",
    2. connected: ->
    3. # Called when the subscription is ready for use on the server
    4. disconnected: ->
    5. # Called when the subscription has been terminated by the server
    6. received: (data) ->
    7. $('#messages').append data['message']
    8. # Called when there's incoming data on the websocket for this channel
    9. speak: (message) ->
    10. @perform 'speak', message: message
    11. $(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
    12. if event.keyCode is 13 # return = send
    13. App.room.speak event.target.value
    14. event.target.value = ""
    15. event.preventDefault()

    App.room里定义了四个方法,除了speak,connected、disconnected、received都是actioncable定义的。
    这几个方法可以和RoomChannel里的方法对应起来,比如:
    connected和subscribed对应,表示客户端和服务器端连接之后的情况。
    disconnected和unsubscribed对应,表示客户端和服务器端失去连接之后的情况。
    received表示从服务器接收到信息之后的情况。因为服务器总是要向客户端推送信息的,接收完信息之后,就可以在这里进行一些页面上的操作,比如DOM更新等。
    room.coffee文件中有重要的一行App.room.speak event.target.value,当键入聊天信息,一按回车键后,就会通过这行代码,把聊天信息,发送到后端服务器,并且会被room_channel.rb中的speak接收,执行Message.create! content: data[‘message’]命令。
    2.4 activejob

    现在还没真正完成,还差一部分。
    room.coffee文件中有一个received方法,它有一行指令$(‘#messages’).append data[‘message’]。
    这个表示当聊天信息发出时,会在聊天信息展示界面上添加聊天的内容。
    现在来处理这个,我们通过activejob来处理,还记得之前的app/views/messages/_message.html.erb文件吗,现在要发挥它的作用。
    先建立一个job。

    1. $ rails g job message_broadcast

    修改app/jobs/message_broadcast_job.rb文件,内容如下:

    1. class MessageBroadcastJob < ApplicationJob
    2. queue_as :default
    3. def perform(message)
    4. ActionCable.server.broadcast 'room_channel', message: render_message(message)
    5. end
    6. private
    7. def render_message(message)
    8. ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message })
    9. end
    10. end

    还要在一个地方执行这个job,是当创建完message的时候。
    修改app/models/message.rb文件,内容如下:

    1. class Message < ApplicationRecord
    2. after_create_commit { MessageBroadcastJob.perform_later self }
    3. end

    做完这一切,重启一下服务器。
    现在来看下效果:
    8、actioncable 入门 - 图3
    本篇完结。