c++ - 我的文件服务器程序已失效的僵尸进程

标签 c++ c linux sockets fork

我有一个基于套接字的文件服务器程序,它使用 fork一次为多个客户提供服务。当我执行 ps -ef 时运行此服务器程序后我收到很多<defunct>我的服务器的子进程。我该如何解决这个问题?下面是我的服务器代码。 提前致谢。

/* This is the server for a very simple file transfer
   service.  This is a "concurrent server" that can
   handle requests from multiple simultaneous clients.
   For each client:
    - get file name and check if it exists
    - send size of file to client
    - send file to client, a block at a time
    - close connection with client
*/

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

#define MY_PORT_ID 60841 
#define MAXLINE 256
#define MAXSIZE 262144 

#define ACK                   2
#define NACK                  3
#define REQUESTFILE           100
#define COMMANDNOTSUPPORTED   150
#define COMMANDSUPPORTED      160
#define BADFILENAME           200
#define FILENAMEOK            400

int writen(int sd,char *ptr,int size);
int readn(int sd,char *ptr,int size);

main()  {

   int sockid, newsd, pid, clilen;
   struct sockaddr_in my_addr, client_addr;   
    long int milsecS, milsecE;

   //printf("server: creating socket\n");
   if ((sockid = socket(AF_INET,SOCK_STREAM,0)) < 0)
     {printf("server: socket error : %d\n", errno); exit(0); }

   //printf("server: binding my local socket\n");
   bzero((char *) &my_addr,sizeof(my_addr));
   my_addr.sin_family = AF_INET;
   my_addr.sin_port = htons(MY_PORT_ID);
   my_addr.sin_addr.s_addr = htons(INADDR_ANY);
   if (bind(sockid ,(struct sockaddr *) &my_addr,sizeof(my_addr)) < 0)
     {printf("server: bind  error :%d\n", errno); exit(0); }
   //printf("server: starting listen \n");
   if (listen(sockid,5) < 0)
     { printf("server: listen error :%d\n",errno);exit(0);}                                        

   while(1==1) { 
     /* ACCEPT A CONNECTION AND THEN CREATE A CHILD TO DO THE WORK */
     /* LOOP BACK AND WAIT FOR ANOTHER CONNECTION                  */
     //printf("server: starting accept\n");
     clilen=sizeof((struct sockaddr *) &client_addr);
     if ((newsd = accept(sockid ,(struct sockaddr *) &client_addr,
                                      &clilen)) < 0)
        {printf("server: accept  error :%d\n", errno); exit(0); }

    /* START TIME */
    struct timeval tvBegin, tvEnd;
    struct timeval *tvB, *tvE;

    gettimeofday(&tvBegin, NULL);
    tvB=&tvBegin;
    milsecS=(tvB->tv_sec)*1000000L-tvB->tv_usec;
    //printf("Start in milliseconds: %ld\n", milsecS);   

        //printf("server: return from accept, socket for this ftp: %d\n",
                                       //newsd);
     if ( (pid=fork()) == 0) {
         /* CHILD PROC STARTS HERE. IT WILL DO ACTUAL FILE TRANSFER */
         close(sockid);   /* child shouldn't do an accept */
         doftp(newsd, milsecS);
         close (newsd);
         exit(0);         /* child all done with work */
         }
      /* PARENT CONTINUES BELOW HERE */
     close(newsd);        /* parent all done with client, only child */
     }              /* will communicate with that client from now on */
}   


/* CHILD PROCEDURE, WHICH ACTUALLY DOES THE FILE TRANSFER */
doftp(int newsd, long int milsecS)
  {       
    int i,fsize,fd,msg_ok,fail,fail1,req,c,ack;
    int no_read ,num_blks , num_blks1,num_last_blk,num_last_blk1,tmp;
    char fname[MAXLINE];
    char out_buf[MAXSIZE];
    FILE *fp;
    long int sMilsec, milsecE;
    sMilsec=milsecS;
    float schDelay; //To calculate schedule delay
    char schDelayS[30]; 
    struct timeval tvEnd;
    struct timeval *tvE;

     no_read = 0;
     num_blks = 0;
     num_last_blk = 0; 


     /* START SERVICING THE CLIENT */ 

     /* get command code from client.*/
     /* only one supported command: 100 -  get a file */
     req = 0;
     if((readn(newsd,(char *)&req,sizeof(req))) < 0)
     {printf("server: read error %d\n",errno);exit(0);}
     req = ntohs(req);
     //printf("Req is: %d\n", req);
     //printf("server: client request code is: %d\n",req);
     if (req!=REQUESTFILE) {
     printf("server: unsupported operation. goodbye\n");
         /* reply to client: command not OK  (code: 150) */
         msg_ok = COMMANDNOTSUPPORTED; 
         msg_ok = htons(msg_ok);
         if((writen(newsd,(char *)&msg_ok,sizeof(msg_ok))) < 0)
            {printf("server: write error :%d\n",errno);exit(0);}
         exit(0);
         }

     /* reply to client: command OK  (code: 160) */
     msg_ok = COMMANDSUPPORTED; 
     msg_ok = htons(msg_ok);
     if((writen(newsd,(char *)&msg_ok,sizeof(msg_ok))) < 0)
             {printf("server: write error :%d\n",errno);exit(0);}

    fail = FILENAMEOK;
    if((read(newsd,fname,MAXLINE)) < 0) {
        printf("server: filename read error :%d\n",errno);
        fail = BADFILENAME ;
        }

     /* IF SERVER CANT OPEN FILE THEN INFORM CLIENT OF THIS AND TERMINATE */
   //printf("File name is: %s\n", fname);
     if((fp = fopen(fname,"r")) == NULL) /*cant open file*/
        fail = BADFILENAME;

     tmp = htons(fail);
     if((writen(newsd,(char *)&tmp,sizeof(tmp))) < 0)
        {printf("server: write error :%d\n",errno);exit(0);   }
     if(fail == BADFILENAME) {printf("server cant open file\n");
                            close(newsd);exit(0);}
     //printf("server: filename is %s\n",fname);

    req = 0;
    if ((readn(newsd,(char *)&req,sizeof(req))) < 0)
                  {printf("server: read error :%d\n",errno);exit(0);}
    //printf("server: start transfer command, %d, received\n", ntohs(req));


   /*SERVER GETS FILESIZE AND CALCULATES THE NUMBER OF BLOCKS OF 
     SIZE = MAXSIZE IT WILL TAKE TO TRANSFER THE FILE. ALSO CALCULATE
     NUMBER OF BYTES IN THE LAST PARTIALLY FILLED BLOCK IF ANY. 
     SEND THIS INFO TO CLIENT, RECEIVING ACKS */
    //printf("server: starting transfer\n");
    fsize = 0;ack = 0;   
    while ((c = getc(fp)) != EOF) {fsize++;}
    num_blks = fsize / MAXSIZE; 
    num_blks1 = htons(num_blks);
    num_last_blk = fsize % MAXSIZE; 
    num_last_blk1 = htons(num_last_blk);
    if((writen(newsd,(char *)&num_blks1,sizeof(num_blks1))) < 0)
             {printf("server: write error :%d\n",errno);exit(0);}
    //printf("server: told client there are %d blocks\n", num_blks);  
    if((readn(newsd,(char *)&ack,sizeof(ack))) < 0)
        {printf("server: ack read error :%d\n",errno);exit(0); }          
    if (ntohs(ack) != ACK) {
      printf("client: ACK not received on file size\n");
      exit(0);
      }
    if((writen(newsd,(char *)&num_last_blk1,sizeof(num_last_blk1))) < 0)
       {printf("server: write error :%d\n",errno);exit(0);}
    //printf("server: told client %d bytes in last block\n", num_last_blk);  
    if((readn(newsd,(char *)&ack,sizeof(ack))) < 0)
        {printf("server: ack read error :%d\n",errno);exit(0); }
    if (ntohs(ack) != ACK) {
      printf("server: ACK not received on file size\n");
      exit(0);
      }
    rewind(fp);    


    /* ACTUAL FILE TRANSFER STARTS  BLOCK BY BLOCK*/       

    gettimeofday(&tvEnd, NULL);
    tvE=&tvEnd;
    milsecE=(tvE->tv_sec)*1000000L-tvE->tv_usec;
    //printf("End time in milliseconds: %ld\n", milsecE);
    schDelay=fabs((milsecE-sMilsec)/1000);
    htonl(schDelay); 
    //printf("Delay is: %f\n", schDelay);

  for(i= 0; i < num_blks; i ++) { 
      no_read = fread(out_buf,sizeof(char),MAXSIZE,fp);
      if (no_read == 0) {printf("server: file read error\n");exit(0);}
      if (no_read != MAXSIZE)
              {printf("server: file read error : no_read is less\n");exit(0);}
      if((writen(newsd,out_buf,MAXSIZE)) < 0)
                 {printf("server: error sending block:%d\n",errno);exit(0);}
      if((readn(newsd,(char *)&ack,sizeof(ack))) < 0)
                 {printf("server: ack read  error :%d\n",errno);exit(0);}
      if (ntohs(ack) != ACK) {
          printf("server: ACK not received for block %d\n",i);
          exit(0);
          }
     // printf(" %d...",i);
      }

   if (num_last_blk > 0) { 
      //printf("%d\n",num_blks);
      no_read = fread(out_buf,sizeof(char),num_last_blk,fp); 
      if (no_read == 0) {printf("server: file read error\n");exit(0);}
      if (no_read != num_last_blk) 
            {printf("server: file read error : no_read is less 2\n");exit(0);}
      if((writen(newsd,out_buf,num_last_blk)) < 0)
                 {printf("server: file transfer error %d\n",errno);exit(0);}
      if((readn(newsd,(char *)&ack,sizeof(ack))) < 0)
             {printf("server: ack read  error %d\n",errno);exit(0);}
      if (ntohs(ack) != ACK) {
          printf("server: ACK not received last block\n");
          exit(0);
          }
      }
   // else printf("\n");

   /* FILE TRANSFER ENDS */
   //printf("server: FILE TRANSFER COMPLETE on socket %d\n",newsd);
   fclose(fp);
   close(newsd);
  }


/*
  TO TAKE CARE OF THE POSSIBILITY OF BUFFER LIMMITS IN THE KERNEL FOR THE
 SOCKET BEING REACHED (WHICH MAY CAUSE READ OR WRITE TO RETURN FEWER CHARACTERS
  THAN REQUESTED), WE USE THE FOLLOWING TWO FUNCTIONS */  

int readn(int sd,char *ptr,int size)
{         int no_left,no_read;
          no_left = size;
          while (no_left > 0) 
                     { no_read = read(sd,ptr,no_left);
                       if(no_read <0)  return(no_read);
                       if (no_read == 0) break;
                       no_left -= no_read;
                       ptr += no_read;
                     }
          return(size - no_left);
}

int writen(int sd,char *ptr,int size)
{         int no_left,no_written;
          no_left = size;
          while (no_left > 0) 
                     { no_written = write(sd,ptr,no_left);
                       if(no_written <=0)  return(no_written);
                       no_left -= no_written;
                       ptr += no_written;
                     }
          return(size - no_left);
}

最佳答案

成为僵尸进程:

当子进程完成其工作并终止时,父进程存在并且没有为它调用 wait(),那么它就变成僵尸进程。

如何删除僵尸:

  • 让父进程在子进程上调用wait(),然后内核会正确地清除终止的进程,而不是让它变成僵尸。
  • 或者,终止父进程,然后 init 进程将成为其父进程,并在终止时正确终止并清除子进程。

就您而言:

因此,可能您使用 fork() 创建了子进程,而没有为其创建父进程 wait(),并且父进程没有终止。 这样,子进程就完成了它的工作,变成了僵尸进程。

您可以尝试在子进程上使用wait()


针对评论中的问题

在哪里添加wait():

  • 首先你需要根据你的逻辑决定使用哪个等待,有wait()/waitpid()/waitid(),其中 wait() 是最简单的一种,控制最少。如果等待时可以阻塞父进程,那么 wait() 就可以,否则需要 waitpid()waitid()。有关每个函数的详细信息,请参阅 man 页。
  • 决定使用哪个等待之后,通常在fork()之后、main()结束之前添加等待函数,可能使用一个循环来处理多个子进程处理,并检查wait的返回值来决定下一步。

书籍: man 页面非常好,但是 Linux 编程书籍(例如 TLPI)可能会提供很大的帮助。


代码示例

这是我在学习linux编程时编写的一个简单的测试程序,它创建多个子进程并等待它们。

wait_test.c

// wait() test
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>

int wait_test() {
    pid_t cpid;
    int i, max_child = 5;

    // create child process,
    for(i=1; i<=max_child; i++) {
        switch(cpid=fork()) {
            case -1: // failed
                printf("error while fork()\n");
                exit(errno);
            case 0: // success, child process goes here
                sleep(i);
                printf("child [%d], going to exit\n",(int)getpid());
                _exit(EXIT_SUCCESS);
                break;
            default: // success, parent process goes here
                printf("parent [%d], child created [%d]\n", (int)getpid(), (int)cpid);
                break;
        }
    }

    // wait child to terminate
    int status;
    while(1) {
        if((cpid = wait(&status)) == -1) {
            if(errno == ECHILD) {
                printf("no more child\n");
                exit(EXIT_SUCCESS);
            } else {
                printf("error while wait()\n");
                exit(-1);
            }
        }
        printf("parent [%d], child [%d] exit with [%d]\n", (int)getpid(), (int)cpid, status);
    }

    return 0;
}

int main(int argc, char *argv[]) {
    wait_test();
    return 0;
}

在 Linux 上编译: gcc -Wall wait_test.c

执行: ./a.out

它提供了子进程和等待如何工作的基本 View 。


@更新:SIGCHLD信号

正如@alk评论的那样,使用SIGCHLD信号处理程序是调用wait的另一种方式,它可能更简单,但似乎有技巧,而且很容易得到错误。

以下是基于我对使用SIGCHLD信号的有限知识的一些描述,仅将其作为提示,而不是确切的示例。

如何使用SIGCHID信号:

默认情况下,SIGCHID 信号会被进程忽略,但可以在其上注册处理程序。

由于标准信号暂时被阻止且未排队,意味着可以发送多个信号并且进程可能只会看到它发生一次, 因此,每当收到此信号时,应使用带有 WNOHANG 选项的 waitpid() 循环来处理所有终止的子进程。

处理函数可能如下所示:

void sigchld_handler() {
    int local_errno = errno; // backup errno,

    while (waitpid(-1, NULL, WNOHANG) > 0)
        continue;

    errno = local_errno; // restore errno,
}

并在调用folk()之前在父进程上注册处理程序,方法是:

if(signal(SIGCHLD, sigchld_handler) == SIG_ERR) {
    printf("error signal()");
    return errno;
}

然后,当子进程终止时,父进程会收到SIGCHLD信号,并调用处理函数,等待子进程,从而帮助清除子进程,而不是让它成为一个僵尸。

关于c++ - 我的文件服务器程序已失效的僵尸进程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33325998/

相关文章:

c++ - Emacs、Cedet 和语义

c - var 与 *var 上的 gdb/x 命令

linux - 替代 solaris 库?

c - 检查内核模块中的 '.read' 函数时出错

c++ - 模板;构造函数;编译时间

PHP 字符串到 "unsigned long"(来自 C++)

c++ - 同一类对象之间的分支预测

c++ - 我怎样才能返回数组 C++?

C - 如何将 int 转换为 uint8_t?

linux - 运行 chroot 命令时出现错误