如何阻止在Ruby中读取命名管道?

我正在尝试设置一个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并再次循环,滚动控制台窗口的顶部。

我试过的两件事:

  1. 我用“r”和“r +”打开了输入fifo – 我有同样的问题;

  2. 我试图确定我的写作过程是否正在发送EOF(我听说过会导致读取的fifo关闭) – AFAIK它不是。

一些背景:

如果它有所帮助,这里是我正在尝试做的事情的“大图”视图:

我正在开发一款运行在RGSS中的游戏,这是一款基于Ruby的游戏引擎。 由于它没有很好的集成调试,我想在游戏运行时设置实时日志 – 当游戏中发生事件时,我希望消息显示在侧面的控制台窗口中。 我可以使用类似于上面的编写器代码的代码将Ruby游戏代码中的事件发送到命名管道; 我现在正在尝试设置一个单独的进程,该进程将等待事件显示在管道中并在控制台到达时显示它们。 我甚至不确定我是否需要Ruby来做这件事,但这是我能想到的第一个解决方案。

请注意,我正在使用来自cygwin的mkfifo ,我碰巧安装了它; 我想知道这可能是我麻烦的根源。

如果它对任何人都有帮助,这就是我在’读者’过程中看到的irb:

 irb(main):001:0> input = File.open("mypipe", "r") => # irb(main):002:0> x = input.gets => nil irb(main):003:0> x = input.gets => nil 

我不希望002和003的input.gets立即返回 – 我希望它们阻止。

我找到了一个解决方案,完全避免使用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 (有关安装gem的详细参考,请参阅此处 )。 将上面保存为“RGSSMonitor.rb”并从命令行调用它作为ruby RGSSMonitor.rb

注意事项:

  1. 上面列出的RGSS代码很脆弱; 特别是,它不会处理打开命名管道的失败。 这通常不是您自己的开发机器上的问题,但我不建议您提供此代码。
  2. 我没有对它进行测试,但我怀疑如果你在日志中写了很多东西而没有运行读取管道的过程(例如RGSSMonitor.rb ),你会遇到问题。 Windows命名管道具有固定大小(我将其设置为1K),默认情况下,一旦管道填满,写入将阻塞(因为没有进程通过读取来减轻压力)。 不幸的是,RPGXP引擎将终止已经停止运行10秒的Ruby脚本。 (我被告知RPGVX已经取消了这个看门狗function – 在这种情况下,游戏将挂起而不是突然终止。)

可能发生的是写入过程正在退出,并且由于没有其他写入过程,EOF被发送到管道,导致gets返回nil ,因此您的代码不断循环。

为了解决这个问题,通常只需在阅读器端打开管道读写即可。 这适用于我(在Mac上),但不适合你(你已尝试过"r""r+" )。 我猜这是与Cygwin相关的( POSIX表示打开FIFO读写是未定义的 )。

另一种方法是打开管道两次 ,一次只读,一次只写。 你没有使用只写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