这一直困扰着我一段时间 - 似乎应该完全可行,但我被卡住了。
我有一个小型的Ruby程序,它只是作为中间人。它运行很长(几分钟),阻塞动作(通过FFI接口),但是应该通过DDP连接通过回调将其从该动作通过回调发送到主Meteor应用程序。
该程序的两个组件都可以自行运行。通过我自己推出的系统,以及metybur gem,我能够与Meteor应用程序进行通信。而且,如果我只使用puts
从FFI接口的回调中输出数据,我也可以完美地得到这些数据。 (除了,由于另一个原因,我无法完全指责,如果FFI /阻止操作位于Thread.new
块中,它将无声地失败。)
但是,出于某种原因,当我尝试将数据发送到Meteor应用程序时,没有任何反应。 ws.send
(在EventMachine上)返回true
,但实际上从未实际调用过,即使我将它放在自己的Thread.new
块中。
我的一部分嫌疑人(虽然无法弄清楚如何测试)连接丢失,因为Ruby应用程序在阻止期间无法处理ping / pong keepalive请求。
我已经尝试从EventMachine EM.spawn
进行阻止过程,我尝试在自己的线程中启动EventMachine,但似乎没有任何效果。
如果有类似这样的最佳实践,即使在CPU密集型阻塞操作期间能够保持应用程序的EventMachine部分响应,也很好奇吗?
答案 0 :(得分:1)
<强> EDITED 强>
在评论中讨论之后,我决定查看我发布的代码并编写一个小型DDP封装,利用Iodine's Websocket Client(我更喜欢,因为我是作者)。
我必须承认,我真的很开心思考这个问题。 附件是使用碘的Meteor连接器的简化代码。这是真正基本的,仅包括:如果丢弃更新连接,完成握手并回答乒乓球。
要将此代码与第一个答案中启动的FFI工作流程概念一起使用,请使用:
# create the connection to the Meteor server
# and setup the callback for incoming messages:
meteor_ddp = IodineDDP.new('ws://chat.n-k.de/websocket') do |message|
Iodine.debug "got message #{message}, it's a Hash"
end
# next, create a dedicated thread for the FFI,
# it will run until the FFI had finished
# or the application exits.
Thread.new do
# initialize FFI interface
data = StringIO.new "initialize FFI interface - StringIO will be our data for now"
# imagine it takes time
sleep 1
# Meteor will respond to these with error messages
(meteor_ddp << data.read(3)) && sleep(0.2) until data.eof?
sleep 1
Iodine.signal_exit
end
# it seems Meteor sends pings and since we already answer them in the
# class object, it should be enough...
# but we can do it too, if we want to:
Iodine.run_every(5) { meteor_ddp << {msg: :ping}.to_json }
对于Meteor DDP连接类,这可能是这样实现的:
require 'iodine/client'
class IodineDDP
attr_reader :session
attr_reader :server_id
def initialize url, &block
@url = url
@ddp_initialized = false
@session = nil
@server_id = nil
@block = block
@closed = false
connect_websocket
end
def << message
Iodine.debug "Writing message #{message}"
ensure_connection
@ws << message
end
alias :write :<<
def close
@closed = true
@ws.on_close { nil }
@ws.close
end
protected
def on_message data
# make sure the DDP handshake is complete
return handshake data unless @ddp_initialized
data = JSON.parse(data)
Iodine.debug "Got message: #{data}"
return write({msg: 'pong', id: data['id'] }.to_json) if data['msg'] == 'ping'
return true if data['msg'] == 'pong'
@block.call data
end
def on_close
@ddp_initialized = false
connect_websocket
end
def ensure_connection
return true unless @ws.closed? || !@ddp_initialized
raise 'This DDP instance was shutdown using `close`, it will not be renewed' if @closed
raise 'DDP disconnected - not enough threads to ensure reconnection' if (@ws.closed? || !@ddp_initialized) && Iodine.threads == 1
timeout = Iodine.time + 3
sleep 0.2 until @ddp_initialized && Iodine.time <= timeout
raise 'DDP disconnected - reconnection timed-out.' if @ws.closed? || !@ddp_initialized
end
def connect_websocket
@___on_message_proc ||= method(:on_message)
@___on_close_proc ||= method(:on_close)
@ws = ::Iodine::Http::WebsocketClient.connect(@url, on_message: @___on_message_proc, on_open: @___on_open_proc, on_close: @___on_close_proc)
# inform
Iodine.debug "initiating a new DDP connection to #{@url}"
# start the DDP handshake
handshake
end
def handshake last_message = nil
raise 'Handshake failed because the websocket was closed or missing' if @ws.nil? || @ws.closed?
unless last_message # this is the first message sent
Iodine.debug "Meteor DDP handshake initiated."
msg = {msg: "connect", version: "1", support: ["1"]}
msg[:session] = @session if @session
return(@ws << msg.to_json)
end
message = JSON.parse(last_message)
raise "Meteor DDP connection error, requires version #{message['version']}: #{last_message}" if message['msg'] == 'failed'
if message['msg'] == 'connected'
# inform
Iodine.debug "Meteor DDP handshake complete."
@session = message['session']
return @ddp_initialized = true
else
return @server_id = message['server_id'] if message['server_id']
Iodine.error "Invalid handshake data - closing connection."
close
end
end
end
# we need at least two threads for the IodineDDP#ensure_connection
Iodine.threads = 3
# # if we are inside a larger application, call:
# Iodine.force_start!
# # if we are on irb:
exit
# no need to write anything if this is the whole of the script