os.execute执行命令时不继承父进程的文件描述符。
我有一个类似于这里描述的问题:
基本上,在我的Lua脚本中,我正在生成另一个脚本,它:
- 无需沟通双向与我的脚本
- 在我的脚本完成后继续运行
- 是第三方程序,其代码我无法控制
问题在于,我的Lua脚本打开了一个TCP套接字以侦听特定端口,并且即使在明确的 server:close()
之后,子进程(或更具体地说,其子代)仍持有套接字,并保持端口处于打开状态 (处于LISTEN状态),从而防止我的脚本再次运行。
以下是演示问题的示例代码:
需要('socket')
打印( '听')
s = socket.bind(“*”,9999)
s:settimeout(1)
当真时
打印( '接受连接')
local c = s:accept()
如果c then
c:settimeout(1)
local rec = c:receive()
打印( '收到' ..回收)
c:close()
如果REC ==“quit”则break end
如果REC ==“exec”则
打印( '在后台运行ping')
os执行('sleep 10s&')
break
end
end
end
打印( '关闭服务器')
s:close()
如果我运行上面的脚本并且 echo quit | nc localhost 9999
一切都很好 - 程序退出并关闭了端口。
但是,如果我执行 echo exec | nc localhost 9999
该程序退出,但端口被生成的 sleep
阻塞(通过 netstat -lpn
确认)直到其退出。
我如何在尽可能简单的情况下解决这个问题,最好不要添加任何额外的依赖项。
原文链接 https://stackoverflow.com/questions/4835608
我找到了一个更简单的解决方案,它利用了 os.execute(cmd)
运行 cmd
在一个 shell
中的特性,正如在这里所看到的那样,shell
能够关闭文件描述符:
http://linux.die.net/man/1/ash(Redirections 部分)
http://www.gnu.org/software/bash/manual/bashref.html#Redirections
例如(在 ash
中测试):
exec 3<&- # 关闭 fd3
exec 3<&- 4<&- # 关闭 fd3 和 fd4
eval exec `seq 1 255 | sed -e 's/.*/&<\&-/'` # 关闭所有文件描述符
因此,在我基于 luasocket
的示例中,只需将:
os.execute('sleep 10s &')
替换为:
os.execute("eval exec `seq 1 255 | sed -e 's/.*/&<\\&-/'`; sleep 10s &")
这将在执行实际命令之前关闭所有文件描述符,包括我的服务器套接字,以避免在我的脚本退出后占用该端口。它还有额外的好处,完成了 stdout
和 stderr
重定向。
这比绕过 Lua
的限制要更紧凑和简单,不需要任何额外的依赖项。感谢嵌入式 Linux 团队在 #uclibc 中提供的最终 shell 语法方面的卓越帮助。
POSIX的做法是使用 FD_CLOEXEC 标志,使用 fcntl(2) 来设置文件描述符。设置后,所有子进程都不会继承标记有该标志的文件描述符。
原始的Lua没有 fcntl 功能,但可以使用之前介绍的 [lua posix] (https://github.com/rrthomas/luaposix) 模块添加它。以你的示例为例,你必须将开始更改为以下内容:
require('socket')
require('posix')
s = socket.bind("*", 9999)
posix.setfl(s, posix.FD_CLOEXEC)
s:settimeout(1)
请注意,我在 luaposix 源中找不到 FD_CLOEXEC 常量,所以您可能也需要手动添加它。
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?
- addEventListener 返回 nil Lua
- Lua中获取用户配置主目录的跨平台方法
- 如何编写 Lua 模式将字符串(嵌套数组)转换为真正的数组?
我不确定如果你想保持
s:close
仅在整个程序的末尾,你是否能够这样做。如果你将它移到os.execute
之前,可能会成功,因为你无论如何都在使用break
(但你可能在你的真实程序中没有这样做)。编辑以获得清晰度:实际问题在于,在这种情况下,你生成子进程的唯一位置是通过使用os.execute()
,并且你无法控制sleep
的子环境,其中一切都从主程序继承,包括套接字和文件描述符。因此,在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. 当
exec
失败时,该行print('exec failed with rc:'..rc);
会抱怨类型问题。我实际上不知道lua,所以你必须解决这个问题。 :)此外,fork()
可能会失败,返回-1。出于完整性,您在主代码中也应该检查这一点。