我遇到类似于此处描述的问题: Prevent fork() from copying sockets
基本上,在我的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
确认),直到退出为止。
我如何以最简单的方式解决这个问题,最好不要添加任何额外的依赖关系。
答案 0 :(得分:4)
我找到了一个更简单的解决方案,利用os.execute(cmd)
在cmd
中运行shell
的事实,事实证明,它能够关闭文件描述符,如下所示:
例如(在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语法的精彩帮助。
答案 1 :(得分:2)
如果您只想在整个程序结束时保留s:close
,我不确定您是否能够这样做。你可能会在os.execute
之前将其移动到成功,因为无论如何你都是break
(但你可能不会在你的真实程序中这样做)。 为了清晰起见而编辑:实际问题是,在这种情况下,您唯一产生子流程的地方是使用os.execute()
,而您无法控制睡眠的子环境,其中所有内容都从主程序继承而来,包括套接字和文件描述符。
因此,在POSIX上执行此操作的规范方法是使用fork(); close(s); exec();
而不是system()
(aka,os.execute
)作为system()
/ os.execute
将在执行期间挂起当前进程状态,并且在子进程中被阻止时您将无法关闭它。
因此,建议抓住luaposix,并使用其posix.fork()
和posix.exec()
功能,并在s:close()
ed子进程中调用fork
。不应该这么糟糕,因为您已经依赖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。当exec失败时,行print('exec failed with rc: ' .. rc);
抱怨一次类型问题。我实际上并不知道lua,所以你必须解决这个问题。 :)此外,fork()
可能会失败,返回-1。为了完整性,也可以在主代码中检查它。
答案 2 :(得分:1)