关闭子进程中打开的套接字

3
我有一个SIP服务器(守护进程),它正在侦听TCP套接字5060。现在,在此父进程中,我创建一个子进程并在子进程中执行某些操作。现在,当我关闭父进程中的此TCP套接字并尝试再次创建时(假设我在此服务器上禁用和启用SIP),创建此套接字会给我带来错误。我已经调试了这个问题并找到了根本原因。根本原因是当子进程被创建时,它继承(获得)所有打开的fd /套接字的副本。当父进程关闭TCP套接字时,它仍然在子进程中打开(ref_counter!= 0),因此我无法在父进程中再次打开套接字!!
现在,我想要的通用解决方案是-一旦启动子进程,它就会检查任何打开的fd(类型为IPv4 / TCP),并将它们关闭,以便该子进程对父进程没有任何副作用。如何在C-unix中实现这一点? 我考虑过通过system(lsof | grep : | awk)获取文件描述符,但我该如何关闭它们? 有没有其他解决办法可以在子进程中关闭套接字?是否有一种方法可以传递端口号并给我已经创建的fd?
我不想要的解决方案是(对我没有帮助的)- 1.在父进程中,最初创建TCP套接字时使用某些标志,以便它们不被子进程复制。 (我不能修改父进程中的套接字创建)! 2.在创建子进程时从父进程传递文件描述符。我无法这样做,因为我没有那个fd。解决方案必须是需要放置在子进程中的东西!
谢谢

我不确定在不编辑父进程代码的情况下是否可能实现。每当我遇到这个问题时,我都使用了你提供的解决方案2,但你似乎不想用它。 - Chris L
1
如果使用Linux,一种可能的方法是:检查/proc/<pid>/fd/中的链接。关闭连接到“socket:[inode]”的描述符。 - user861537
2
我怀疑你误诊了根本原因,现在正在寻找两个不良解决方案来解决自己制造的问题。对于子进程来说,关闭监听套接字应该是微不足道的事情,而对于父进程来说,在分叉后立即关闭数据套接字也应该是微不足道的事情。 - Duck
@MichaelBrennan - 谢谢。我只是有点困惑这些链接(它们只是数字,我将使用awk解析lsof或/proc/<pid>/fd的输出)是否可以直接传递给close(int fd)。但是,我刚刚尝试了一下,它可以工作。 - Adam
2
OP,不要误会,但如果你在使用awk解析lsof来关闭C程序中的套接字,那么你正在直接走向歧途。你需要考虑重新设计或提供一个非常有说服力的理由来证明这是必要的。 - Duck
@Duck,你为什么这样说?还有其他方法可以找到从父进程继承的套接字文件描述符吗(你的反对只是针对awk工具吗)?另外,请注意,此子进程不是为了处理客户端-服务器架构而创建的。该子进程是为了处理SIP服务器(父进程)的一些非关键诊断而创建的。 - Adam
3个回答

4

您拥有文件描述符。只需关闭不需要的那些!

在子进程中,应关闭监听套接字。

在父进程中,应关闭已接受的套接字(=新连接)。


3

子进程会继承父进程的打开文件描述符(包括套接字)。在您的情况下,子进程也打开了套接字,并绑定到了端口上。因此,在正常情况下,没有其他进程可以再监听该端口。您需要做的是在子进程中关闭套接字(或者在 fork() 调用之前,在父进程中关闭它,如果在之后不再需要它)。


1

以下是一种在您的子进程中确定文件描述符是否为套接字的方法。

由于子进程将继承fd表,只需迭代测试每个FD。下面程序中的子进程通过getrlimit获取fd表的最大大小,并迭代遍历表以确定每个文件描述符是否为(a)已打开的、(b)套接字并且如果是(c)监听套接字。父进程只是在分叉之前打开一个监听套接字和一个常规套接字(用于测试),然后等待子进程。

您应该能够使用此概述来实现您的目标,而无需诸如awk之类的工具。

#define _BSD_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <string.h>
#include <netdb.h>


int isListeningSocket(int fd)
{
    int retval;
    socklen_t len = sizeof(retval);

    if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &retval, &len) != -1)
        if (retval)
            return(1);

    return(0);
}

int main (int argc, char *argv[])
{
    //create a listening socket
    int lsock = socket(AF_INET,SOCK_STREAM, 0);

    struct sockaddr_in serverinfo;
    memset(&serverinfo, '0', sizeof(serverinfo));
    serverinfo.sin_family=AF_INET;
    serverinfo.sin_port=htons(atoi("9999"));
    serverinfo.sin_addr.s_addr=INADDR_ANY;

    int ret;

    if ((ret = bind(lsock,(struct sockaddr *) &serverinfo, sizeof(serverinfo))) == -1)
    {
        perror("bind");
        exit(1);
    }

    if ((ret = listen(lsock,1000)) == -1)
    {
        perror("listen");
        exit(1);
    }

    //create a regular socket
    int rsock = socket(AF_INET,SOCK_STREAM, 0);

    int pid = fork();

    if (pid == -1)
    {
        perror("fork");
        exit(1);
    }

    if (pid) //parent
    {
        wait(NULL);
        exit(0);
    }

    //child ----------

    struct rlimit rlim;

    if ((ret = getrlimit(RLIMIT_NOFILE, &rlim)) == -1)
    {
        perror("getrlimit");
        exit(1);
    }

    int maxFD = rlim.rlim_cur;

    for (int i = 0; i < maxFD; ++i)
    {
       struct stat statbuf;

       if (fstat(i, &statbuf) == -1)
           if (errno == EBADF)
           {
               printf("file descriptor %d is not open\n", i);
               continue;
           }
           else
           {
               perror("fstat");
               exit(1);
           }

       if (S_ISSOCK(statbuf.st_mode))
           if (isListeningSocket(i))
              printf("file descriptor %d is a LISTENING socket\n", i);
           else
               printf("file descriptor %d is a REGULAR socket\n", i);
       else
           printf("file descriptor %d is NOT a socket\n", i);
    }

    return 0;
}

完全没有必要。在fork()之后,子进程具有与父进程相同的数据和变量。子进程只需要关闭代码中称为“lsock”或其他名称的套接字,而父进程只需要关闭代码中称为“csock”或其他名称的套接字。至于你所说的“rsock”,我完全不明白它的作用。 - user207421
@EJP - 我知道。我两天前就这么说了,但是这就是 OP 坚持要做的。 - Duck
@EJP - 如果您在发出不必要和粗鲁的负面评价之前花费10秒钟来考虑一下,那么这种情况就不会逃脱您的注意。 - Duck
@Duck 非常感谢。虽然我现在不在家,还不能尝试这个方法,但是getrlimit和fstat确实是对我有帮助的,并且是正确的解决方案。 - Adam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接