如何使用 Rails 通过 Web 套接字发送二进制文件 [英] How to send binary file over Web Sockets with Rails

查看:27
本文介绍了如何使用 Rails 通过 Web 套接字发送二进制文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a Rails application where users upload Audio files. I want to send them to a third party server, and I need to connect to the external server using Web sockets, so, I need my Rails application to be a websocket client.

我正在研究如何正确设置它.我还没有致力于任何 gem,但是 'faye-websocket'宝石看起来很有希望.我什至在在超时前在 websocket 中发送大文件",但是,使用该代码对我不起作用.

I'm trying to figure out how to properly set that up. I'm not committed to any gem just yet, but the 'faye-websocket' gem looks promising. I even found a similar answer in "Sending large file in websocket before timeout", however, using that code doesn't work for me.

这是我的代码示例:

@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
}

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

When I use that, I get an error from the recipient server:

No JSON object could be decoded

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

This makes sense, because the I don't believe it's properly formatted for faye-websocket. Their documentation says:

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

send(message) accepts either a String or an Array of byte-sized integers and sends a text or binary message over the connection to the other peer; binary data must be encoded as an Array.

我不知道如何做到这一点.如何使用 Ruby 将二进制加载到整数数组中?

I'm not sure how to accomplish that. How do I load binary into an array of integers with Ruby?

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

I tried modifying the send command to use the bytes method:

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 感到困惑.

I know my file is 286KB, so something is wrong here. I get confused as to when to use File.read vs File.open vs. File.new.

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

Also, maybe this gem isn't the best for sending binary data. Does anyone have success sending binary files in Rails with websockets?

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

Update: I did find a way to get this working, but it is terrible for memory. For other people that want to load small files, you can simply File.binread and the unpack method:

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

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

However, if I use that same code on a mere 100MB file, the server runs out of memory. It depletes the entire available 1.5GB on my test server! Does anyone know how to do this is a memory safe manner?

推荐答案

这是我的看法:

# 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 的作者,我写的用于 Plezi(一个 RESTful Websocket 实时应用程序框架,您可以单独使用或在 Rails 中使用)...我非常有偏见;-)

It's only fair to mention that I'm Iodine's author, which I wrote for use in Plezi (a RESTful Websocket real time application framework you can use stand alone or within Rails)... I'm super biased ;-)

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

I would avoid the gets because it's size could include the whole file or a single byte, depending on the location of the next End Of Line (EOL) marker... read gives you better control over each chunk's size.

这篇关于如何使用 Rails 通过 Web 套接字发送二进制文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆