Rails 我写了 WebSocket 和 ActionCable 相关的序列文章

hfpp2012 · 发布于 2016年5月05日 · 最后由 n5ken 回复于 2016年8月08日 · 5729 次阅读
16154
本帖已被设为精华帖!

从websocket介绍开始,到actioncable的介绍,应用,最后到部署,一共写了10篇关于websocket的文章。

下面是目录:

1. websocket之入门(一)

2. websocket之客户端与服务器端的交互(二)

3. websocket之客户端详解(三)

4. websocket之实现简易聊天室(四)

5. websocket之用tubesock在rails实现聊天室(五)

6. websocket之message_bus(六)

7. websocket之actioncable入门(七)

8. websocket之actioncable进阶(八)

9. websocket之actioncable实现重新连接功能(九)

10. websocket之部署(十)

把第七篇列在ruby-china预览一下,如果有兴趣我也可以把每篇文章都发到ruby-china上。

websocket之actioncable入门(七)

1. 介绍

websocket的序列文章重点要讲的就是actioncable,之前也讲了好多关于各种方式实现聊天室的文章,相信从中,也能学到好多关于websocket实践的知识和经验,这节要来讲讲actioncable。

actioncable是集成在rails 5中的一个功能,它能够轻易的在rails中使用websocket。现在先把actioncable用起来,再慢慢研究其原理和特性。

2. 使用

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

2.1 聊天室界面

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

$ rails new actioncable_demo
$ cd actioncable_demo

这样就生成了一个新项目。

接着创建message这个model,存储聊天记录。

$ rails g model message content:text
$ rails db:migrate

创建聊天室的界面。

在config/routes.rb中添加路由。

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

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

class RoomsController < ApplicationController
  def show
    @messages = Message.all
  end
end

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

<h1>Chat room</h1>

<div id="messages">
  <%= render @messages %>
</div>

<form>
  <label>Say something:</label><br>
  <input type="text" data-behavior="room_speaker">
</form>

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

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

到目前为止,按照之前的经验,界面都建立好了,如下图所示:

2.2 开启websocket

接下来,就是要来处理websocket部分。

先在客户端浏览器中开启websocket请求。

actioncable默认提供了一个文件app/assets/javascripts/cable.coffee,把几行注释打开,就可以开启websocket,内容如下:

#
#= require action_cable
#= require_self
#= require_tree ./channels
#
@App ||= {}
App.cable = ActionCable.createConsumer()

其实这些js的内容很简单,它做的主要的事情就是前面几篇文章所讲的在客户端浏览器执行new WebSocket,具体的内容可以查看其源码。

还要在路由中添加下面这行,把websocket服务以engine的方式挂载起来。

mount ActionCable.server => '/cable'

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

2.3 channel

现在要让客户端和服务器端连接起来。

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

先新建一个channel。

$ rails g channel room speak

修改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

  def speak(data)
    # ActionCable.server.broadcast "room_channel", message: data['message']
    Message.create! content: data['message']
  end
end

其中定义了三个方法,分别是subscribedunsubscribedspeak

subscribedunsubscribed方法是默认就生成的,而speak是我们自己定义的。

subscribed表示的是当客户端连接上来的时候使用的方法。

unsubscribed表示的是当客户端与服务器失去连接的时候使用的方法。

还有,app/assets/javascripts/channels/room.coffee文件,内容如下:

App.room = App.cable.subscriptions.create "RoomChannel",
  connected: ->
    # Called when the subscription is ready for use on the server

  disconnected: ->
    # Called when the subscription has been terminated by the server

  received: (data) ->
    $('#messages').append data['message']
    # Called when there's incoming data on the websocket for this channel

  speak: (message) ->
    @perform 'speak', message: message

$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
  if event.keyCode is 13 # return = send
    App.room.speak event.target.value
    event.target.value = ""
    event.preventDefault()

App.room里定义了四个方法,除了speakconnecteddisconnectedreceived都是actioncable定义的。

这几个方法可以和RoomChannel里的方法对应起来,比如:

connectedsubscribed对应,表示客户端和服务器端连接之后的情况。

disconnectedunsubscribed对应,表示客户端和服务器端失去连接之后的情况。

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。

$ rails g job message_broadcast

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

class MessageBroadcastJob < ApplicationJob
  queue_as :default

  def perform(message)
    ActionCable.server.broadcast 'room_channel', message: render_message(message)
  end

  private
    def render_message(message)
      ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message })
    end
end

还要在一个地方执行这个job,是当创建完message的时候。

修改app/models/message.rb文件,内容如下:

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

做完这一切,重启一下服务器。

现在来看下效果:

本篇完结。

下一篇: websocket之actioncable进阶(八)

共收到 37 条回复
25910

已收藏,谢谢楼主

15420

一上就加精了。赞

15470

特来支持阁下👍

96

支持楼主,actioncable很好

7614

32个赞~

3679

websocket一直看的断断续续,楼主做的6

24293

rails 4也可以用actioncable的呀。

16154

#7楼 @xiaohesong 以前可以的,不过那个actioncable gem被移除掉了,是空的 https://github.com/rails/actioncable rails 4用tubesock也是一样的

24293

#8楼 @hfpp2012 前几天试了是可以的。archive分支。

16154

#9楼 @xiaohesong 原来是在另外的分支,多谢告知

19195

风神发的么

17004

在下佩服。在下要好好跟随阁下的步伐

19195

鸟兄 没事到处浪

18411

我也要跟上了。进入rails 5 fighting!

19195

风神 到处浪

8658

#13楼 @matrixbirds 在下也佩服的五体投地 @hfpp2012 阁下人中龙风 在下一直很惦记

8755

@hfpp2012 链接到“websocket之actioncable进阶(八)“的地址有误,仍然是第七篇的地址。

20楼 已删除
9967

不考虑性能,不用activejob也可以吧?

16154

#21楼 @thumbor 可以的呀,没有关系的

23794

好文章,mark

5841

雕堡了

16184

Mark了,有空拜读

2898

最近也在使用 actioncable ,但是发现actioncable似乎没办法实现socket.io里比较简单的几个方法:

// sending to sender-client only
socket.emit('message', "this is a test");

// sending to all clients except sender
 socket.broadcast.emit('message', "this is a test");

// sending to individual socketid
 socket.broadcast.to(socketid).emit('message', 'for your eyes only');

不知道有无办法解决?

2898

也就是说有什么办法能 perform 之后收到一个后端的返回?

好像只能在每个perform后带一个uuid,后端接到后都 stream_from uuid,处理好结果后返回给uuid,前端收到一次数据后断开

16154

#26楼 @xlaok 这个是可以实现的,我不懂socket.io,如果我没理解错的话,这几条命令就是给规定的客户端广播消息

ActionCable.server.broadcast \
  "web_notifications_#{current_user.id}", { title: 'New things!', body: 'All the news that is fit to print' }

地址在这里:https://github.com/rails/rails/tree/master/actioncable#channel-example-2-receiving-new-web-notifications

其实也可以想一下ruby-china就知道了,你登录之后,在消息那里情况每个人肯定是不一样的,这个时候就是通过current_user.id来区别,也可以这样说,channel就是可以接参数的,channel就相当于一个个的房间,隔开消息的。

16154

#27楼 @xlaok 你下面这个问题我没有弄清楚你想表达的

2898

#28楼 @hfpp2012 对,但是这个实际上只是开了一个不同名字的room而已,如果要保证 每个session,或者每个request独立,写起来是很别扭的(实际上真的遇到了这样的需求),而socket.io比较方便

2898

#29楼 @hfpp2012 下面问题的意思是:

前端调用

away: ->
    # Calls `AppearanceChannel#away` on the server
    @perform("away")

可以调用 AppearanceChannel#away ,但是调用之后,如果 AppearanceChannel#away 想再给这个sender回一个消息,就做不到了。

而 socket.io 可以这样

socket.on('records', function(sheet_id){
  records = Record.all
  //直接返回records给sender
  socket.emit('records', records);
});

actioncable 有办法这样做吗?

16154

#31楼 @xlaok 我明白你的意思了,这个是可以做到的,你先可以看这篇文章 http://www.rails365.net/articles/websocket-zhi-yong-tubesock-zai-rails-shi-xian-liao-tian-shi-wu

tubesock这个gem可以很好的做到你说的需求,不过,actioncable只是另一种实现,离不开本质问题

websocket本质这是tcp协议,你只要能够将这个socket请求取出来,就用write方法写一些数据就可以了。

我在本地试了一下,我截图

之所以我会知道这个,是因为我查看了actioncable的源码,使用的主要命令就是

connection.env['rack.hijack_io']

返回的是socket文件描述符。

针对这个描述符进行writer操作就可以了,数据自然会返回给客户端。而不广播,是单一的。

9592

Rails 5 还不敢用。。现在用nodejs 的ws模块 + rabbitmq/redis pubsub 来做

2898

@hfpp2012 楼主大大,你对 actioncable 的单元测试有没有经验?

96

点击链接发现404

16154

#36楼 @n5ken 修复好了

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册