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