2011-01-29 33 views
5

我有类似于这里所描述的一个问题: Prevent fork() from copying socketsos.execute没有继承父母的FDS

基本上,我的Lua脚本中,我产卵另一个脚本:

  • 不需要与我的脚本通信两种方式
  • 继续我的剧本已经完成
  • 后运行是一个第三方程序,代码我没有控制权

的问题是,我的Lua脚本打开一个TCP套接字来侦听特定端口上,它的退出后尽管明确server:close()孩子(或者更具体地说其子女)持有到插座和保持端口打开(处于LISTEN状态),防止我的脚本再次运行。

这里的示例代码演示该问题:

require('socket') 

print('listening') 
s = socket.bind("*", 9999) 
s:settimeout(1) 

while true do 
    print('accepting connection') 
    local c = s:accept() 
    if c then 
      c:settimeout(1) 
      local rec = c:receive() 
      print('received ' .. rec) 
      c:close() 
      if rec == "quit" then break end 
      if rec == "exec" then 
        print('running ping in background') 
        os.execute('sleep 10s &') 
        break 
      end  
    end 
end 
print('closing server') 
s:close() 

如果我运行上面的脚本和echo quit | nc localhost 9999一切正常 - 程序退出和港口关闭。

但是,如果我做echo exec | nc localhost 9999程序退出,但端口被产生的sleep(如netstat -lpn确认)阻塞,直到它退出。

如何解决这个以最简单的方式,最好是不添加任何额外的依赖。

回答

3

我发现了一个更简单的解决方案,它利用了os.execute(cmd)运行cmdshell,其中,因为事实证明,可关闭的文件描述符这里看到的事实:


例如(在ash测试):

exec 3<&-          # closes fd3 
    exec 3<&- 4<&-         # closes fd3 and fd4 
    eval exec `seq 1 255 | sed -e 's/.*/&<\&-/'` # closes all file descriptors 

所以在我的luasocket基于例如它足以替代:

os.execute('sleep 10s &') 

有:

os.execute("eval exec `seq 1 255 | sed -e 's/.*/&<\\&-/'`; sleep 10s &") 

这会在执行实际命令(此处为sleep 10s)之前关闭所有文件描述符,包括我的服务器套接字,以便在我的脚本退出后不会占用端口。它也有服用stdoutstderr重定向护理奖金。

这是不是围绕Lua的限制,工作更加紧凑和简单,并且不需要任何额外的依赖。由于去#uclibc在那里我得到了一些精彩的帮助与嵌入式linux船员最终shell语法。

+0

干得好!有意愿的地方,有一个方法:-)很高兴你也回来了一个更新。 –

2

我不确定你是否可以这样做,如果你想保持s:close只在整个程序的结尾。无论如何,你可能会成功地将它移动到os.execute之前,因为无论如何你都是break(但你可能没有在你的真实程序中这样做)。 编辑为清楚起见:实际的问题是,你产卵在这种情况下一个子进程,唯一的地方是使用os.execute(),和你有过睡眠的孩子环境没有控制,在这种一切从主程序继承包括套接字和文件描述符。

所以,规范的方式做到这一点的POSIX是用fork(); close(s); exec();,而不是system()(又名os.execute)作为system()/os.execute将在执行过程中挂到当前进程的状态,您将无法关闭它,同时在子进程中被阻止。

因此,建议将是抢luaposix,并使用其posix.fork()posix.exec()功能,并在fork版子进程调用s:close()。不应该那么糟糕,因为您已经依靠luasocket来使用外部软件包。


编辑:这里的大量注释代码luaposix做到这一点:

require('socket') 
require('posix') 

print('listening') 
s = socket.bind("*", 9999) 
s:settimeout(1) 

while true do 
    print('accepting connection') 
    local c = s:accept() 
    if c then 
      c:settimeout(1) 
      local rec = c:receive() 
      print('received ' .. rec) 
      c:close() 
      if rec == "quit" then break end 
      if rec == "exec" then 
        local pid = posix.fork() 
        if pid == 0 then 
         print('child: running ping in background') 
         s:close() 
         -- exec() replaces current process, doesn't return. 
         -- execp has PATH resolution 
         rc = posix.execp('sleep','60s'); 
         -- exec has no PATH resolution, probably "more secure" 
         --rc = posix.exec('/usr/bin/sleep','60s'); 
         print('exec failed with rc: ' .. rc); 
        else 
         -- if you want to catch the SIGCHLD: 
         --print('parent: waiting for ping to return') 
         --posix.wait(pid) 
         print('parent: exiting loop') 
        end 
        break; 
      end 
    end 
end 
print('closing server') 
s:close() 

这将关闭在子进程插座调用exec之前,和netstat -nlp输出显示系统是正确无父母退出时,在9999端口更长时间地收听。

P.S.当执行失败行print('exec failed with rc: ' .. rc);抱怨一类问题一次。我其实不知道lua,所以你必须解决这个问题。 :)此外,fork()可能会失败,返回-1。为了完整性,也许应该在你的主代码中检查它。

+0

好吧,这是进步,我暗暗希望它不会来'fork()ing'。 'luaposix'作为dep的很好,因为它被嵌入到目标平台上的解释器 - OpenWrt。 我们的例子中的问题'sleep'占用的端口消失,但已被替换为2个: - 如果你拿走了'break' fork'后'和'发送到exec'端口单曲:接受( )'不再超时,它的意思是非阻塞的。 - 如果用'ping'替换'sleep',它的输出将与脚本输出相同,这是不理想的。 这些可能超出了原始问题的范围,但相关。 – koniu

+0

对不起,myopenid在那里下了一会儿。我根据你的其他问题玩了更多的代码,我认为这超出了我的知识。我在'execp()'之前尝试了'io.output('/ dev/null')'和'io.stdout:close()',但没有运气。这里有一些相关的信息:[stackoverflow](http://stackoverflow.com/questions/1466064)和[lua手册](http://www.lua.org/pil/21.1.html)。在'wait()'方面,我检查了'luaposix'的源代码,并没有实现'WNOHANG',所以这可能是非阻塞的问题。如果没有人回应这个问题,你可以在这些问题上发布另一个Q. –

+0

我在这里找到了stdout问题的解决方案:http://lua-users.org/wiki/HiddenFeatures。这关闭了标准文件:'local f = assert(io.open'/ dev/null'); debug.setfenv(io.stdout,debug.getfenv(f)); F:关闭(); assert(io.stdout:close())' – koniu

1

POSIX方法是使用fcntl (2)用FD_CLOEXEC标志设置文件描述符。设置后,所有子流程将不会继承标有该标志的文件描述符。

股票Lua没有fcntl功能,但可以将其与lua posix模块一起添加,如前面的答案中所介绍的。把你的例子,你就必须改变开始为这样的:

require('socket') 
require('posix') 
s = socket.bind("*", 9999) 
posix.setfl(s, posix.FD_CLOEXEC) 
s:settimeout(1) 

请注意,我没有找到在luaposix源FD_CLOEXEC不变,所以你可能必须通过手工添加它。