首页 > 系统相关 >父/子进程文件描述符继承机制导致socket bind失败的问题

父/子进程文件描述符继承机制导致socket bind失败的问题

时间:2023-08-18 19:03:54浏览次数:39  
标签:socket bind 描述符 fd 18 进程 addr

此问题来自项目上,应用程序本身由它的父进程启动,父进程监听SIGCHLD信号,即子进程退出时,父进程会收到这个信号,然后立即通过execlp重新启动子进程,确保子进程异常崩溃会被重新拉起来。而子进程(我们实际的业务应用)也会在某些地方fork新的进程,干别的事情。

出现的问题是,进程被重新拉起来后,一个socket的bind动作失败,错误为bind: Address already in use。netstat查看,发现是crond占用了这个端口。最开始觉得比较奇怪,crond按道理不会使用socket,更不可能恰好绑定这个端口。并且还发现crond进程的/proc/$(pidof crond)/fd居然打开了显卡设备节点,这个就完全不可能了。打开显卡的行为是我们的应用程序,这两者有什么关联呢?查看代码发现,我们的应用会fork子进程,然后执行shell命令/etc/init.d/crond restart。经同事提醒,子进程会继承父进程打开的文件描述符!原来问题在这里,几年前看APUE(Unix环境高级编程)时,确实记得这一点,太久没搞忘记了。第8章 <进程控制>提到的这点。

为了加深映像,模拟测试验证一下:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>

int main()
{
    int fd;
    pid_t pid;
    struct sockaddr_in addr;

    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0){
        perror("socket");
        return -1;
    }

    memset(&addr, 0, sizeof(struct sockaddr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(4567);

    if (bind(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0){
        perror("bind");
        close(fd);
        return -1;
    }

    if (listen(fd, 5) < 0){
        perror("listen");
        close(fd);
        return -1;
    }

    pid = fork();
    if (pid == 0){
        printf("I am child\n");
        while (1)
        {
            sleep(1);
        }
    }else if (pid > 0){
        printf("I am parent\n");
        close(fd);
        return 0;
    }else{
        perror("fork");
        close(fd);
        return -1;
    }

    close(fd);

    return 0;
}

上面代码父进程中bind 4567端口,然后fork后,父进程退出,子进程继续运行,此时子进程成为孤儿进程,由1号进程托管,在ubuntu20.04上是由systemd托管。先查看成为孤儿进程的子进程打开的文件描述符:

ls /proc/$(pidof ctest)/fd -l
total 0
lrwx------ 1 a a 64 Aug 18 18:02 0 -> '/dev/pts/2 (deleted)'
lrwx------ 1 a a 64 Aug 18 18:02 1 -> '/dev/pts/2 (deleted)'
lrwx------ 1 a a 64 Aug 18 18:02 2 -> '/dev/pts/4 (deleted)'
lrwx------ 1 a a 64 Aug 18 18:02 3 -> 'socket:[28406147]'

netstat -antp | grep 4567
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:4567            0.0.0.0:*               LISTEN      3535349/ctest

发现子进程确实继承了父进程打开的文描述符,并且端口的占用也继承了。再次启动程序

./ctest
bind: Address already in use

问题复现。如何解决这个问题呢?

  • man socket可知,socket的第二个参数type,可以通过OR的形式指定bit标识,具体参数为SOCK_CLOEXEC,它表示socket创建的fd在exec时,做close动作。即代码改为:
fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);

编译重新验证,先杀掉最开始的成为孤儿进程的子进程。重复验证过程,问题确认得到解决。

  • 以此类推,如果不是socket,是其他类型的东西,例如文件,设备节点等。则可以在open时,指定flags:O_CLOEXEC,或者对fd进行fcntl操作
open(path, O_RDWR | O_CLOEXEC)

或者开时不指定,后续通过fcntl更改flags
int flags = fcntl(fd, F_GETFD);  
flags |= FD_CLOEXEC;  
fcntl(fd, F_SETFD, flags);
  • 还有一种情况,父进程调用第三方库,第三方库未指定O_CLOEXEC标识,而我们又不想子进程继承打开的描述符,避免误操作到,引发不必要的麻烦,此时可以通过clone方式,而不是fork来创建子进程,clone可以指定标志,选择继承父进程的哪些东西,例如CLONE_FILES控制是否继承父进程打开的文件描述符,我们这里可以选择不继承。

  • 手动关闭文件描述符,fork和exec之间是允许我们做自己想做的事情,例如在这里,我们关闭所有文件描述符,一个典型的参考例子时AUEP中守护进程里面的例子,先获得进程最大的文件描述符编号,然后逐个close。

struct rlimit rl;
getrlimit(RLIMIT_NOFILE, &rl);

for(i=0;i<rl.rlim_max; i++)
{
    close(i);
}

标签:socket,bind,描述符,fd,18,进程,addr
From: https://www.cnblogs.com/thammer/p/17640540.html

相关文章

  • call,apply,bind的区别
    1.三者都可用于改变函数中this指向,但又有细微区别2.三者的语法传参大致相同,第一个参数表示跟谁建立链接,从第二个参数开始传入具体参数值,但其中apply需要用数组进行传入3.call和apply都可直接调用函数,但bind是返回一个新函数......
  • WPF ListBox 控件绑定 Binding
     当我们需要用到循环的列表内容,并且模板化程度高的时候,建议使用 ListBox 来做绑定。XAML:<Window.DataContext><local:VMTempTest/></Window.DataContext><StackPanelMargin="10,0,0,50"Orientation="Vertical"><TextBlockText="ListBo......
  • 直播系统源码协议探索篇(二):网络套接字协议WebSocket
     上一篇我们分析了直播平台的会话初始化协议SIP,他关乎着直播平台的实时通信和多方互动技术的实现,今天我们来讲另一个协议,叫网络套接字协议WebSocket,WebSocket基于TCP在客户端与服务器建立双向通信的网络协议,并且可以通过单个长连接实现。在直播系统源码平台已经成为人们获取知识......
  • 直播系统源码协议探索篇(二):网络套接字协议WebSocket
    上一篇我们分析了直播平台的会话初始化协议SIP,他关乎着直播平台的实时通信和多方互动技术的实现,今天我们来讲另一个协议,叫网络套接字协议WebSocket,WebSocket基于TCP在客户端与服务器建立双向通信的网络协议,并且可以通过单个长连接实现。在直播系统源码平台已经成为人们获取知识、放......
  • xshell隧道SOCKET代理
    访问数据流本地浏览器->本地代理->linux隧道服务器->真实服务隧道代理chrome安装插件SwitchyOmegahttps://pan.baidu.com/s/1O9gDwDLK906G-i2G3hmSug?pwd=gkkglinkshttps://www.cnblogs.com/lq0710/p/16631218.htmlhttps://www.bbsmax.com/A/QV5ZyqwbJy/https......
  • DataBinding开始使用以及布局详解
    DataBinding开始使用了解如何为您的开发环境支持使用DataBinding,包括在AndroidStudio中支持数据绑定代码。DataBinding提供了灵活性和广泛的兼容性-它是一个支持库,因此您可以将其用于运行Android4.0(API14级)或更高版本的设备。我们建议在您的项目中使用最新的AndroidGradle插件......
  • python rasa聊天机器人教程三:基于WebSocket的简单网页组件配置
    1.准备环境新建一个目录,并且在命令行中进入该目录初始化一个Rasa项目,使用以下命令:rasainit2.修改Rasa的配置在Rasa项目目录中,找到credentials.yml文件,添加以下内容:socketio:user_message_evt:user_utteredbot_message_evt:bot_utteredsession_persistenc......
  • linux系统句柄限制调整,当使用netty/socket触发达到系统最大连接数时查看
    socket原理:客户端使用tcp端口连接至服务端,服务端会打开一个句柄文件和客户端保持连接,注意并不是一个连接就会占用一个服务器端口,所以socket连接数跟系统端口最大连接数无关,不然系统防火墙不就没啥用,默认系统每个进程打开的句柄是有限制的,另外整个系统还有一个句柄限制总数,所以soc......
  • Nginx支持websocket的配置详解
     目录一、对wss与nginx代理wss的理解:二、Nginx支持websocket的配置一、对wss与nginx代理wss的理解:1、wss协议实际是websocket+SSL,就是在websocket协议上加入SSL层,类似https(http+SSL)。2、利用nginx代理wss【通讯原理及流程】客户端发起wss连接连到nginxnginx......
  • 网络编程day01--socket套接字
    进程间通信-socket套接字基本特征:socket是一种接口技术,被抽象了一种文件操作,可以让同一计算机中的不同进程之间通信,也可以让不同计算机中的进程之间通信(网络通信)本地进程间通信编程模型:进程A                                        ......