我有一个在 tcp 和 udp 端口 5060 上侦听的 SIP 服务器的示例应用程序。在代码中的某个位置,我执行了一个 system("pppd file /etc/ppp/myoptions &");
之后,如果我执行 netstat -apn,它显示端口 5060 也为 pppd 开放!有什么方法可以避免这种情况吗?这是Linux中系统功能的标准行为吗?
谢谢,埃利森
是的,默认情况下,无论何时分叉一个进程(system
确实如此),子进程都会继承父进程的所有文件描述符。如果孩子不需要这些描述符,它应该关闭它们。使用system
(或执行 fork+exec 的任何其他方法)执行此操作的方法是在您的子进程不应使用的所有文件描述符上设置 FD_CLOEXEC 标志。这将导致它们在任何子执行某个其他程序时自动关闭。
一般来说,任何时候你的程序都会打开任何类型的文件描述符,这些文件描述符会存在很长一段时间(例如你的例子中的监听套接字),并且不应该与孩子共享,你应该这样做
fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
在文件描述符上。
从 2016 年开始?在 POSIX.1 的修订版中,您可以SOCK_CLOEXEC
在创建套接字时使用标志或'd 到套接字的类型中以自动获取此行为:
listenfd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, 0);
bind(listenfd, ...
listen(listemfd, ...
即使其他一些同时运行的线程执行system
or fork
+exec
调用,也可以保证它会正确关闭。幸运的是,这个标志在 Linux 和 BSD unix 上已经支持了一段时间(不幸的是 OSX 不支持)。
您可能应该system()
完全避免使用该功能。它本质上是危险的,因为它调用了 shell,它可以被篡改并且相当不可移植,即使在 Unicies 之间也是如此。
你应该做的是fork()/exec()
舞蹈。它是这样的
if(!fork()){
//close file descriptors
...
execlp("pppd", "pppd", "file", "/etc/ppp/myoptions", NULL);
perror("exec");
exit(-1);
}
是的,这是fork()
在 Linuxsystem()
中实现的标准行为。
从socket()
调用返回的标识符是一个有效的文件描述符。该值可用于面向文件的函数,例如read()
、write()
、ioctl()
和close()
。
相反,每个文件描述符都是一个套接字,这是不正确的。无法打开常规文件open()
并将该描述符传递给,例如, bind()
或listen()
。
当您调用system()
子进程时,继承与父进程相同的文件描述符。这就是子进程继承stdout
(0)、stdin
(1) 和(2) 的方式。stderr
如果您安排打开一个文件描述符为 0、1 或 2 的套接字,则子进程将继承该套接字作为标准 I/O 文件描述符之一。
您的子进程从父进程继承每个打开的文件描述符,包括您打开的套接字。
正如其他人所说,这是程序所依赖的标准行为。
在防止它方面,您有几个选择。fork()
正如戴夫建议的那样,首先是在 之后关闭所有文件描述符。其次,POSIX 支持使用fcntl
withFD_CLOEXEC
为每个 fd 设置一个“close on exec”位。
最后,由于您提到您在 Linux 上运行,因此有一组更改旨在让您在打开内容时正确设置位。当然,这取决于平台。可以在http://udrepper.livejournal.com/20407.html找到概述
这意味着您可以在套接字创建调用中使用按位或使用“类型”来设置SOCK_CLOEXEC
标志。前提是您运行的是内核 2.6.27 或更高版本。
system()
复制当前进程,然后在其上启动一个子进程。(当前进程不再存在。这可能是 pppd 使用 5060 的原因。您可以尝试fork()/exec()
创建一个子进程并保持父进程处于活动状态。