我有一个示例应用程序,是一个同时监听tcp和udp端口5060的SIP服务器。 在代码的某个点上,我执行了system("pppd file /etc/ppp/myoptions &");
执行了这个命令之后,如果我运行netstat -apn命令,它会显示出pppd也打开了5060端口! 有没有什么方法可以避免这种情况发生?这是Linux系统函数的标准行为吗?
谢谢, Elison
默认情况下,每当您分叉一个进程(system
这样做时),子进程都会继承所有父进程的文件描述符。如果子进程不需要这些描述符,则应该将它们关闭。使用 system
(或任何执行分叉+执行的方法)的方法是,在不应由您的进程的子进程使用的所有文件描述符上设置 FD_CLOEXEC 标志。这将导致它们在任何子进程执行其他程序时自动关闭。
一般来说,无论何时您的程序打开任何类型的文件描述符,并且该描述符将存活一段时间(例如您示例中的监听套接字),并且不应与子进程共享,您都应该执行此操作。
fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
从POSIX.1的2016年修订版开始,您可以将SOCK_CLOEXEC
标志或'd应用于套接字的类型,以便在创建套接字时自动获得此行为:
在文件描述符上。
listenfd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, 0);
bind(listenfd, ...
listen(listemfd, ...
这可以确保即使其他同时运行的线程执行了`system`或`fork`+`exec`调用,它也会被正确关闭。幸运的是,这个标志在Linux和BSD Unix上已经支持了一段时间(不幸的是,在OSX上并不支持)。
您应该避免使用system()
函数。它本质上是危险的,因为它会调用shell,可以被篡改且不太可移植,即使在不同的Unix系统之间也是如此。
您应该使用fork()/exec()
来实现。步骤如下:
if(!fork()){
//close file descriptors
...
execlp("pppd", "pppd", "file", "/etc/ppp/myoptions", NULL);
perror("exec");
exit(-1);
}
是的,这是Linux中fork()
的标准行为,system()
也是由它实现的。
socket()
调用返回的标识符是一个有效的文件描述符。该值可用于文件导向的函数,例如read()
、write()
、ioctl()
和close()
。
反之,每个文件描述符都是套接字的说法并不正确。不能使用open()
打开常规文件,然后将该描述符传递给bind()
或listen()
等函数。
当您调用system()
时,子进程会继承与父进程相同的文件描述符。这就是子进程如何继承stdout
(0)、stdin
(1)和stderr
(2)的方式。如果您安排使用文件描述符为0、1或2的套接字,则子进程将继承该套接字作为标准I/O文件描述符之一。
您的子进程继承了父进程中打开的每个文件描述符,包括您打开的套接字。
正如其他人所述,这是程序依赖的标准行为。
当涉及到防止它时,您有几个选项。首先是像Dave建议的在fork()
后关闭所有文件描述符。其次,有使用fcntl
与FD_CLOEXEC
一起使用的POSIX支持,以在每个fd基础上设置“close on exec”位的方法。
最后,由于您提到正在运行Linux,因此有一组更改旨在让您在打开事物的时候就正确地设置该位。自然,这取决于平台。可以在http://udrepper.livejournal.com/20407.html找到概述。
这意味着您可以在套接字创建调用中使用位或运算符来设置SOCK_CLOEXEC
标志。前提是您正在运行2.6.27或更高版本的内核。
system()
函数会复制当前进程并在其之上启动一个子进程。(当前进程已经不存在了。这可能就是pppd使用5060的原因。你可以尝试使用fork()/exec()
来创建一个子进程并保持父进程活着。
system()
使用了fork()
/exec()
。 - Heath Hunnicuttsystem()
执行后会产生2个进程?父进程和子进程? - harisystem()
之后,会有两个进程,即父进程和子进程 -- 但是调用还没有返回。system()
的实现会在子 PID 上调用 waitpid
。在 pppd
的情况下,该进程会自我后台化(使用 fork
/exec
而不带有 waitpid
),因此瞬间会有 三个 进程,其中两个是 pppd,一个是父进程。当 system
返回时,子进程已经退出,因为 C 中的返回值包含由生成程序提供给 exit
的值。一旦 system
返回,子进程必须已经退出。 - Heath Hunnicutt
O_CLOEXEC
标志。这样可以避免进行单独的调用,并使操作原子化,如果另一个线程(或信号处理程序)在open
调用和fcntl
调用之间进行 fork-and-exec,则此操作很重要。 - R.. GitHub STOP HELPING ICE