os.execute没有继承父的fds

我有一个类似于这里描述的问题: 防止fork()复制套接字 基本上,在我的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
确认)阻止,直到它退出。 我如何以最简单的方式解决这个问题,最好不要添加任何额外的依赖项。     
已邀请:
我发现了一个更简单的解决方案,利用
os.execute(cmd)
shell
中运行
cmd
的事实,事实证明,它能够关闭文件描述符,如下所示: http://linux.die.net/man/1/ash(重定向部分) http://www.gnu.org/software/bash/manual/bashref.html#Redirections 例如(在
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
),以便在脚本退出后不会占用端口。它还有照顾
stdout
stderr
重定向的奖励。 这比解决
Lua
的限制更加紧凑和简单,并且不需要任何额外的依赖性。感谢#uclibc,我从嵌入式Linux工作人员那里得到了一些关于最终shell语法的精彩帮助。     
如果你想在整个节目结束时保留
s:close
,我不确定你是否能够这样做。你可能会成功将它移到
os.execute
之前,因为你无论如何都要(20((​​但你可能不会在你的真实程序中这样做)。为清晰起见编辑:实际问题是,在这种情况下,您唯一产生子进程的地方是使用
os.execute()
,并且您无法控制睡眠的子环境,其中所有内容都是从主程序继承的,包括套接字和文件描述符。 因此,在POSIX上执行此操作的规范方法是使用
fork(); close(s); exec();
而不是
system()
(aka,
os.execute
),因为
system()
/
os.execute
将在执行期间挂起到当前进程状态,并且您将无法在阻止时关闭它在子过程中。 因此,建议采用luaposix,并使用其
posix.fork()
posix.exec()
功能,并在
fork
ed子进程中调用
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。 附:当执行失败时,行
print('exec failed with rc: ' .. rc);
抱怨一次类型问题。我实际上并不知道lua,所以你必须解决这个问题。 :)此外,
fork()
可能会失败,返回-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常量,因此您可能还需要手动添加它。     

要回复问题请先登录注册