linux父进程fork出子进程后,子进程为何首先需要close文件描述符。

在linux c/c++编程时,父进程fork出子进程后,子进程经常第一件事就是close掉所有的文件描述符;为何需要这样做,本文用一个例子进行简单说明。

考虑到一种情况,父进程创建了tcp服务端套接字,并且listen,此时fork出子进程,若子进程里面不close此监听套接字,则子进程里面是否能接收到客户端的连接。
因为一旦子进程里面也能够接收到客户端的连接,无疑会造成混乱。

答案是可以的,现在构造一个服务端程序,listen后,fork出子进程,然后父进程进入sleep,子进程调用accept,在accept成功后,打印对应的信息。

服务端代码:

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

#define PORT 8888                                    //侦听端口地址:8888
#define BACKLOG 20                                    //侦听队列长度:2

extern void process_conn_server(int s);              //服务器对客户端的处理:读取数据并发送响应字符

int main(int argc,char *argv[])
{
        int ss = 0;                                      //ss = server socket = 服务器socket描述符
    int cs = 0;                                      //cs = client socket = 客户端socket描述符
    struct sockaddr_in server_addr;                  //服务器地址结构
    struct sockaddr_in client_addr;                  //客户端地址结构
    int ret = 0;                                     //返回值
    pid_t pid;
        //进程ID
        char buffer[1024];
        ssize_t size = 0;

    /**
     *  Step 2 : 建立套接字
    */
    ss = socket(AF_INET,SOCK_STREAM,0);              //创建一个AF_INET族的流类型socket
    if(ss < 0)                                       //检查是否正常创建socket
    {
        perror("socket error\n");
        exit(EXIT_FAILURE);
    }
   
    /**
     *  Step 3 : 设置服务器地址
     *  Note:
     *      htonl():将主机数转换成无符号长整型的网络字节顺序
     *      htons():将整型变量从主机字节顺序转变成网络字节顺序
    */
    bzero(&server_addr,sizeof(server_addr));          //清零
    server_addr.sin_family = AF_INET;                 //设置地址族为AF_INET
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //本地地址
        server_addr.sin_port = htons(PORT);               //设置端口号

    /**
     *  Step 4 : 绑定地址结构到套接字描述符
    */
    ret = bind(ss,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if (ret < 0)                                      //出错
    {
        perror("bind error\n");
        exit(EXIT_FAILURE);
    }

    /**
     *  Step 5 : 设置侦听,侦听队列长度为2,可同时处理两个客户端连接请求
    */
    ret = listen(ss,BACKLOG);
    if (ret < 0)                                      //出错
    {
        perror("bind error\n");
        exit(EXIT_FAILURE);
    }

    /**
     *  Step 6 : 主循环过程
    */
    int pid2 = fork();
    if(pid2 > 0)
        {
                sleep (60000);
        }
else
    for(;;)
    {
        /* 接收客户端连接 */
        int addrlen = sizeof(struct sockaddr);
        cs = accept(ss,(struct sockaddr*)&client_addr,&addrlen);
        if(cs < 0)                                    //出错
        {
            continue;                                 //结束本次循环
        }
printf("accept one client in child process\n");
        //sleep(30);

/*
                for(;;)
                {
                        size = read(cs,buffer,1024);                 //从套接字中读取数据放到缓冲区buffer中

                        if(size == 0)                               //没有数据
                        {
                                return;
                        }

                        printf("buffer is %s\n", buffer);
                }
*/



    }
  

客户端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define PORT 8888                                     //端口地址:8888
#define SERVER_IP "10.0.0.64"

extern void process_conn_client(int s);

int main(int argc,char *argv[])
{
    int s = 0;                                        //socket描述符
    struct sockaddr_in server_addr;                   //服务器地址结构
    int ret = 0;                                      //返回值
    char buf[1024] = {0};
    int i = 0;
    strcpy(buf, "hello world");
    /**
     *  Step 2 : 建立套接字
     */
    s = socket(AF_INET,SOCK_STREAM,0);                //创建一个AF_INET族的流类型socket
    if(s < 0)                                         //检查是否正常创建socket
    {
        perror("socket error\n");
        exit(EXIT_FAILURE);
    }

    /**
     *  Step 3 : 设置服务器地址
     */
    memset(&server_addr, 0, sizeof(server_addr));          //清零
    server_addr.sin_family = AF_INET;                 //设置地址族为AF_INET
    inet_pton(AF_INET, SERVER_IP, &(server_addr.sin_addr));
    server_addr.sin_port = htons(PORT);               //设置端口号

    /**
     *  Step 4 : 将用户输入的字符串类型的IP地址转为整型
     */
    //inet_pton(AF_INET,argv[1],&server_addr.sin_addr);

    /**
     *  Step 5 : 连接服务器
     */
    ret = connect(s,(struct sockaddr*)&server_addr,sizeof(struct sockaddr));
    if(ret < 0)
    {
        perror("connect error\n");
        printf("errno is %d\n", errno);
        exit(EXIT_FAILURE);
    }
    printf("connect succeed\n");
    //for(;;)
    {
        //sleep(10000000);
        sprintf(buf, "hello world, %d", i);
        i++;

        ret = write(s,buf,strlen(buf)+1);
        if(ret < 0)
        {
            printf("write failed, errno is %d\n", errno);
        }
        ret = read(s, buf, strlen(buf)+1);
        if(ret < 0)
        {
            printf("read failed, errno is %d\n", errno);
        }

    }
    while(1)sleep(10);
    close(s);                                         //关闭连接
}

启动服务端(ip:10.0.0.64),然后客户端(10.0.0.60)连接,结果打印的信息如下:

在这里插入图片描述

可以看到监听套接字所在的进程20811是父进程,建立连接的套接字位于子进程20812。
所以后续父进程fork出子进程后,子进程需要关闭对应的文件描述符。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值