我试图设置一个Ruby脚本,该脚本从循环中的命名管道读取,阻塞直到管道中有输入。
我有一个定期将调试事件放入命名管道的进程:
# Open the logging pipe
log = File.open("log_pipe", "w+") #'log_pipe' created in shell using mkfifo
...
# An interesting event happens
log.puts "Interesting event #4291 occurred"
log.flush
...
然后我想要一个单独的进程,它将从此管道中读取并在事件发生时将事件打印到控制台。我尝试过使用这样的代码:
input = File.open("log_pipe", "r+")
while true
puts input.gets #I expect this to block and wait for input
end
# Kill loop with ctrl+c when done
我希望input.gets
阻止,耐心等待,直到新的输入到达fifo;但它会立即读取nil
并再次循环,滚动控制台窗口的顶部。
我尝试了两件事:
我用" r"打开了输入的fifo。和" r +" - 我有同样的问题;
我试图确定我的写作过程是否正在发送EOF(我听说会导致读取的fifo关闭) - AFAIK它不是。
一些背景:
如果它有所帮助,那么这里有一张大图片'查看我想要做的事情:
我正在开发一款运行在基于Ruby的游戏引擎RGSS中的游戏。由于它没有很好的集成调试,我想在游戏运行时设置实时日志 - 当游戏中发生事件时,我希望消息显示在侧面的控制台窗口中。我可以使用类似于上面的编写器代码的代码将Ruby游戏代码中的事件发送到命名管道;我现在正在尝试设置一个单独的进程,该进程将等待事件显示在管道中并在控制台到达时显示它们。我甚至不确定我是否需要Ruby来做这件事,但这是我能想到的第一个解决方案。
请注意,我使用了来自cygwin的mkfifo
,我碰巧安装了它;我想知道这可能是我麻烦的原因。
如果它对任何人都有帮助,那么这就是我在用我的读者阅读的内容中所看到的内容'过程:
irb(main):001:0> input = File.open("mypipe", "r")
=> #<File:mypipe>
irb(main):002:0> x = input.gets
=> nil
irb(main):003:0> x = input.gets
=> nil
我不希望002和003处的input.gets
立即返回 - 我希望它们会阻止。
答案 0 :(得分:3)
我找到了一个解决方案,可以完全避免使用Cygwin不可靠的命名管道实现。 Windows有自己的命名管道工具,甚至还有一个名为win32-pipe的Ruby Gem使用它。
不幸的是,似乎没有办法在RGSS脚本中使用Ruby Gems;但是通过剖析win32-pipe gem,我能够将相同的想法融入到RGSS游戏中。此代码是将游戏事件实时记录到后台通道所需的最低限度,但它对深度调试非常有用。
我在'Main'之前添加了一个新的脚本页面并添加了这个:
module PipeLogger
# -- Change THIS to change the name of the pipe!
PIPE_NAME = "RGSSPipe"
# Constant Defines
PIPE_DEFAULT_MODE = 0 # Pipe operation mode
PIPE_ACCESS_DUPLEX = 0x00000003 # Pipe open mode
PIPE_UNLIMITED_INSTANCES = 255 # Number of concurrent instances
PIPE_BUFFER_SIZE = 1024 # Size of I/O buffer (1K)
PIPE_TIMEOUT = 5000 # Wait time for buffer (5 secs)
INVALID_HANDLE_VALUE = 0xFFFFFFFF # Retval for bad pipe handle
#-----------------------------------------------------------------------
# make_APIs
#-----------------------------------------------------------------------
def self.make_APIs
$CreateNamedPipe = Win32API.new('kernel32', 'CreateNamedPipe', 'PLLLLLLL', 'L')
$FlushFileBuffers = Win32API.new('kernel32', 'FlushFileBuffers', 'L', 'B')
$DisconnectNamedPipe = Win32API.new('kernel32', 'DisconnectNamedPipe', 'L', 'B')
$WriteFile = Win32API.new('kernel32', 'WriteFile', 'LPLPP', 'B')
$CloseHandle = Win32API.new('kernel32', 'CloseHandle', 'L', 'B')
end
#-----------------------------------------------------------------------
# setup_pipe
#-----------------------------------------------------------------------
def self.setup_pipe
make_APIs
@@name = "\\\\.\\pipe\\" + PIPE_NAME
@@pipe_mode = PIPE_DEFAULT_MODE
@@open_mode = PIPE_ACCESS_DUPLEX
@@pipe = nil
@@buffer = 0.chr * PIPE_BUFFER_SIZE
@@size = 0
@@bytes = [0].pack('L')
@@pipe = $CreateNamedPipe.call(
@@name,
@@open_mode,
@@pipe_mode,
PIPE_UNLIMITED_INSTANCES,
PIPE_BUFFER_SIZE,
PIPE_BUFFER_SIZE,
PIPE_TIMEOUT,
0
)
if @@pipe == INVALID_HANDLE_VALUE
# If we could not open the pipe, notify the user
# and proceed quietly
print "WARNING -- Unable to create named pipe: " + PIPE_NAME
@@pipe = nil
else
# Prompt the user to open the pipe
print "Please launch the RGSSMonitor.rb script"
end
end
#-----------------------------------------------------------------------
# write_to_pipe ('msg' must be a string)
#-----------------------------------------------------------------------
def self.write_to_pipe(msg)
if @@pipe
# Format data
@@buffer = msg
@@size = msg.size
$WriteFile.call(@@pipe, @@buffer, @@buffer.size, @@bytes, 0)
end
end
#------------------------------------------------------------------------
# close_pipe
#------------------------------------------------------------------------
def self.close_pipe
if @@pipe
# Send kill message to RGSSMonitor
@@buffer = "!!GAMEOVER!!"
@@size = @@buffer.size
$WriteFile.call(@@pipe, @@buffer, @@buffer.size, @@bytes, 0)
# Close down the pipe
$FlushFileBuffers.call(@@pipe)
$DisconnectNamedPipe.call(@@pipe)
$CloseHandle.call(@@pipe)
@@pipe = nil
end
end
end
要使用此功能,您只需确保在编写活动前致电PipeLogger::setup_pipe
;并在游戏退出前致电PipeLogger::close_pipe
。 (我将设置调用放在'Main'的开头,并添加ensure
子句来调用close_pipe
。)之后,您可以在任何时候向PipeLogger::write_to_pipe("msg")
添加呼叫。任何带有“msg”字符串的脚本并写入管道。
我用RPG Maker XP测试了这段代码;它也适用于RPG Maker VX及更高版本。
您还需要从管道中读取内容。有很多方法可以做到这一点,但一个简单的方法是使用标准的Ruby安装,win32-pipe Ruby Gem和这个脚本:
require 'rubygems'
require 'win32/pipe'
include Win32
# -- Change THIS to change the name of the pipe!
PIPE_NAME = "RGSSPipe"
Thread.new { loop { sleep 0.01 } } # Allow Ctrl+C
pipe = Pipe::Client.new(PIPE_NAME)
continue = true
while continue
msg = pipe.read.to_s
puts msg
continue = false if msg.chomp == "!!GAMEOVER!!"
end
我使用Ruby 1.8.7 for Windows和上面提到的win32-pipe gem(请参阅here以获取有关安装宝石的参考资料)。将上面保存为“RGSSMonitor.rb”并从命令行调用ruby RGSSMonitor.rb
。
注意事项:
RGSSMonitor.rb
),你就会遇到问题。 Windows命名管道具有固定大小(我将其设置为1K),默认情况下,一旦管道填满,写入将阻塞(因为没有进程通过读取来减轻压力)。不幸的是,RPGXP引擎将终止已经停止运行10秒的Ruby脚本。 (我被告知RPGVX已经取消了这个看门狗功能 - 在这种情况下,游戏会挂起而不是突然终止。)答案 1 :(得分:1)
可能发生的是写入过程正在退出,并且由于没有其他写入过程,EOF被发送到管道,导致gets
返回nil
,因此您的代码不断循环。
要解决此问题,通常只需在阅读器端打开管道读写即可。这适用于我(在Mac上),但不适合您(您已尝试"r"
和"r+"
)。我猜这是由于Cygwin(POSIX says opening a FIFO read-write is undefined)。
另一种方法是打开管道两次,一次只读,一次只写。你没有使用只写IO来做任何事情,只是因为管道上总是有一个活动的写入器,所以它不会被关闭。
input = File.open("log_pipe", "r") # note 'r', not 'r+'
keep_open = File.open("log_pipe", "w") # ensure there's always a writer
while true
puts input.gets
end