世界上最伟大的投资就是投资自己的教育

全场限时 5 折

首页WebSocket
随风 · 练气

Websocket 学习笔记系列文章教程之 用 tubesock 在 Rails 实现聊天室 (五)

随风发布于5752 次阅读

1. 介绍

前篇文章介绍了如何实现一个简易的聊天室,有时候,我们在 rails 应用中也是需要使用 websocket 的功能,比如,消息的通知,一些数据状态的通知等,所以这篇来介绍下如何简单地实现这个功能。

2. rack hijack

这篇文章主要介绍的是一个比较重要的概念,它是 rack hijack。hijack 是 rack 在 1.5.0 之后才支持,它出现的目的是为了能在 rack 层次对 socket 连接进行操作。能对底层的 socket 进行操作,也就能使用 websocket。puma,unicorn 等服务器都有它的实现。

新建一个文件叫 hijack.ru,内容如下:

use Rack::Lint
use Rack::ContentLength
use Rack::ContentType, "text/plain"
class DieIfUsed
  def each
    abort "body.each called after response hijack\n"
  end

  def close
    abort "body.close called after response hijack\n"
  end
end
run lambda { |env|
  case env["PATH_INFO"]
  when "/hijack_req"
    if env["rack.hijack?"]
      io = env["rack.hijack"].call
      if io.respond_to?(:read_nonblock) &&
         env["rack.hijack_io"].respond_to?(:read_nonblock)

        # exercise both, since we Rack::Lint may use different objects
        env["rack.hijack_io"].write("HTTP/1.0 200 OK\r\n\r\n")
        io.write("request.hijacked")
        io.close
        return [ 500, {}, DieIfUsed.new ]
      end
    end
    [ 500, {}, [ "hijack BAD\n" ] ]
  when "/hijack_res"
    r = "response.hijacked"
    [ 200,
      {
        "Content-Length" : r.bytesize.to_s,
        "rack.hijack" : proc do |io|
          io.write(r)
          io.close
        end
      },
      DieIfUsed.new
    ]
  end
}

其中env['rack.hijack'].call就是返回 socket 的文件描述符的对象,之后可以对这个对象进行像 socket 那样的操作,比如io.write("request.hijacked"),就是返回 “request.hijacked”。

使用下面的指令运行这段代码:

$ unicorn hijack
I, [2016-04-12T15:44:53.197379 #18197]  INFO -- : listening on addr=0.0.0.0:8080 fd=9
I, [2016-04-12T15:44:53.197564 #18197]  INFO -- : worker=0 spawning...
I, [2016-04-12T15:44:53.201453 #18197]  INFO -- : master process ready
I, [2016-04-12T15:44:53.203755 #18226]  INFO -- : worker=0 spawned pid=18226
I, [2016-04-12T15:44:53.204682 #18226]  INFO -- : Refreshing Gem list
I, [2016-04-12T15:44:53.315295 #18226]  INFO -- : worker=0 ready

监听在 8080 端口,可以用浏览器访问。

puma,unicorn 等服务器对 hijack 的实现是很简单的,本来他们就是对 socket 的操作,现在只不过是提供了一个接口,把它放到请求的全局变量中罢了,还增加了一些状态判断。主要是这三个变量env['rack.hijack']env['rack.hijack?']env['rack.hijack_io']

3. Tubesock

tubesock是一个 gem,它就是对上面的rack hijack进行封装,从而能实现 websocket 功能,它不仅能在 rack 中实现,也能在 rails 中的 controller 使用。

现在我们来在 rails 中结合 redis 的 pub/sub 功能实现一个聊天室功能。

首先安装,我们使用 puma 作为服务器。

在 Gemfile 中添加下面几行。

gem 'puma'
gem 'redis-rails'
gem 'tubesock'

添加app/controllers/chat_controller.rb文件,内容如下:

class ChatController < ApplicationController
  include Tubesock::Hijack

  def chat
    hijack do |tubesock|
      redis_thread = Thread.new do
        Redis.new.subscribe "chat" do |on|
          on.message do |channel, message|
            tubesock.send_data message
          end
        end
      end

      tubesock.onmessage do |m|
        Redis.new.publish "chat", m
      end

      tubesock.onclose do
        redis_thread.kill
      end
    end
  end
end

config/routes.rb中添加路由。

Rails.application.routes.draw do
  get "/chat", to: "chat#chat"
end

分别添加 view 和 js。

<h1>Tubesock Chat</h1>
<pre id="output"></pre>
<form class="chat">
  <input placeholder="hello world" autofocus>
</form>
$ ->
  socket = new WebSocket "ws://#{window.location.host}/chat"

  socket.onmessage = (event) ->
    if event.data.length
      $("#output").append "#{event.data}<br>"

  $("body").on "submit", "form.chat", (event) ->
    event.preventDefault()
    $input = $(this).find("input")
    socket.send $input.val()
    $input.val(null)

对上面的代码进行解析:

假如有一个浏览器客户端打开了,就会运行new WebSocket "ws://#{window.location.host}/chat"

这样就到了ChatController中的chat方法。

执行了下面的语句:

redis_thread = Thread.new do
  Redis.new.subscribe "chat" do |on|
    on.message do |channel, message|
      tubesock.send_data message
    end
  end
end

将会开启一个新的线程,并会用 Redis 去订阅一个新的频道chat,进入到subscribe方法中,tubesock.send_data message表示一旦有消息过来就立即用 tubesock 这个 socket 把数据返回给客户端浏览器。

tubesock.onmessage do |m|
  Redis.new.publish "chat", m
end

上面的代码表示一旦服务器接收到客户端浏览器的消息之后的动作,比如说,在聊天界面输入消息内容。接收到消息之后就立即发送到上面所说的chat通道,上面 Redis 中的subscribe动作就会被触发。因为所有的客户端一连上服务器就会执行 Redis 的subscribe功能,也就是说所有浏览器客户端都会触发subscribe里的动作,就会接收到服务器端的推送消息,这也正是聊天界面的效果。

效果如下:

本篇完结。

下一篇:websocket 之 message_bus(六)

本站文章均为原创内容,如需转载请注明出处,谢谢。

0 条回复
暂无回复~~
相关小书
websocket教程

websocket教程

从websocket的介绍开始,从入门到精通

发表于

喜欢
统计信息
    学员: 29063
    视频数量: 1973
    文章数量: 489

© 汕尾市求知科技有限公司 | Rails365 Gitlab | Qiuzhi99 Gitlab | 知乎 | b 站 | 搜索

粤公网安备 44152102000088号粤公网安备 44152102000088号 | 粤ICP备19038915号

Top