如何使用 Rails 通过 Web Sockets 发送二进制文件

Posted

技术标签:

【中文标题】如何使用 Rails 通过 Web Sockets 发送二进制文件【英文标题】:How to send binary file over Web Sockets with Rails 【发布时间】:2015-10-29 22:33:01 【问题描述】:

我有一个 Rails 应用程序,用户可以在其中上传音频文件。我想将它们发送到第三方服务器,并且我需要使用 Web 套接字连接到外部服务器,因此,我需要将我的 Rails 应用程序作为 Websocket 客户端。

我正在尝试弄清楚如何正确设置它。我还没有致力于任何 gem,但 'faye-websocket' gem 看起来很有希望。我什至在“Sending large file in websocket before timeout”中找到了类似的答案,但是,使用该代码对我不起作用。

这是我的代码示例:

@message = Array.new
EM.run 
    ws = Faye::WebSocket::Client.new("wss://example_url.com")

    ws.on :open do |event|
      File.open('path/to/audio_file.wav','rb') do |f|
        ws.send(f.gets)
      end
    end

    ws.on :message do |event|
      @message << [event.data]
    end

    ws.on :close do |event|
      ws = nil
      EM.stop
    end

当我使用它时,我从接收服务器收到一个错误:

No JSON object could be decoded

这是有道理的,因为我认为它的格式不适合 faye-websocket。他们的documentation 说:

send(message) 接受字符串或字节大小的数组 整数并通过连接发送文本或二进制消息到 其他同行;二进制数据必须编码为数组。

我不确定如何做到这一点。如何使用 Ruby 将二进制文件加载到整数数组中?

我尝试修改send 命令以使用bytes 方法:

File.open('path/to/audio_file.wav','rb') do |f|
    ws.send(f.gets.bytes)
end

但现在我收到此错误:

Stream was 19 bytes but needs to be at least 100 bytes

我知道我的文件是 286KB,所以这里有问题。我对何时使用 File.readFile.openFile.new 感到困惑。

另外,也许这个 gem 不是发送二进制数据的最佳选择。有人用 websockets 在 Rails 中成功发送二进制文件吗?

更新:我确实找到了让它工作的方法,但这对记忆来说很糟糕。对于其他想要加载小文件的人,您可以简单地使用File.binreadunpack 方法:

ws.on :open do |event|
  f = File.binread 'path/to/audio_file.wav'
  ws.send(f.unpack('C*'))
end

但是,如果我在仅 100MB 的文件上使用相同的代码,服务器就会耗尽内存。它耗尽了我的测试服务器上所有可用的 1.5GB!有谁知道如何做到这一点是一种内存安全的方式?

【问题讨论】:

【参考方案1】:

这是我的看法:

# do only once when initializing Rails:
require 'iodine/client'
Iodine.force_start!

# this sets the callbacks.
# on_message is always required by Iodine.
options = 
options[:on_message] = Proc.new do |data|
   # this will never get called
   puts "incoming data ignored? for:\n#data"
end
options[:on_open] = Proc.new do
   # believe it or not - this variable belongs to the websocket connection.
   @started_upload = true
   # set a task to send the file,
   # so the on_open initialization doesn't block incoming messages.
   Iodine.run do
      # read the file and write to the websocket.
      File.open('filename','r') do |f|
         buffer = String.new # recycle the String's allocated memory
         write f.read(65_536, buffer) until f.eof?
         @started_upload = :done
      end
      # close the connection
      close
   end
end
options[:on_close] = Proc.new do |data|
   # can we notify the user that the file was uploaded?
   if @started_upload == :done
        # we did it :-)
   else
        # what happened?
   end
end

# will not wait for a connection:
Iodine::Http.ws_connect "wss://example_url.com", options
# OR
# will wait for a connection, raising errors if failed.
Iodine::Http::WebsocketClient.connect "wss://example_url.com", options

公平地说,我是Iodine's 的作者,我写它是为了在Plezi 中使用(一个 RESTful Websocket 实时应用程序框架,您可以单独使用或在 Rails 中使用)...我超级有偏见;-)

我会避免使用gets,因为它的大小可能包括整个文件或单个字节,具体取决于下一个行尾 (EOL) 标记的位置...read 让您可以更好地控制每个块的大小。

【讨论】:

以上是关于如何使用 Rails 通过 Web Sockets 发送二进制文件的主要内容,如果未能解决你的问题,请参考以下文章

使用 juggernaut 作为 rails 插件时遇到问题

如何通过 Procfile 在 Heroku 上禁用 Rails web dyno?

通过io.sockets.sockets解析时如何获取socket id

如何通过 Rails 中的 JSON 接口上传文件

我应该使用 Web Sockets 吗?

如何让 Amazon 的 ELB 与 HTTPS/SSL 与 Web Sockets 一起工作?