网络编程之僵尸进程

18 篇文章 1 订阅
12 篇文章 0 订阅

          在讨论之前先给出一段代码,其中存在一个很大的隐患,就是僵尸进程!!!!可用top命令进行查看,如下图所示:

       

  由上图可看出,存在1个zombie(僵尸进程)!!!

   下面就给出具体的实现:

     config.h:

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

const int MAX_LINE = 2048;
const int PORT = 9877;
const int BACKLOG = 10;
const int LISTENQ = 6666;
const int MAX_CONNECT = 20;

服务器端:

#include "config.h"

int main(int argc,char **argv)
{
    //声明服务器地址和客户链接地址
    struct sockaddr_in servaddr,cliaddr;
 
    //声明服务器监听套接字和客户端链接套接字
    int listenfd,connfd;
    pid_t childpid;

    //声明缓冲区
    char buf[MAX_LINE];

    socklen_t clilen;

    //(1)初始化监听套接字litenfd
    if((listenfd = socket(AF_INET,SOCK_STREAM,0)) < 0){
        perror("socket error");
        exit(1);
    }

    //(2)设置服务器sockaddr_in结构
    bzero(&servaddr,sizeof(servaddr));
    
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(PORT);

    //(3)绑定套接字和端口
    if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0){
        perror("bind error");
        exit(1);
    }
    //(4)监听客户请求
    if(listen(listenfd,LISTENQ) < 0){
        perror("listen error");
    }
    //(5)接受客户端请求
    for(; ;){
        clilen = sizeof(cliaddr);
        if((connfd  = accept(listenfd,(struct sockaddr *)&cliaddr,&clilen)) < 0){
            perror("accept error");
            exit(1);
        }
        //新建子进程单独处理链接
        if((childpid = fork()) == 0){
            close(listenfd);
            //str_echo
            ssize_t n;
            char buff[MAX_LINE];
            while((n = read(connfd,buff,MAX_LINE)) > 0){
                write(connfd,buff,n);
            }
            exit(0);
        }
        close(connfd);
    }
    //(6)关闭监听套接字
    close(listenfd);
}

客户端:

#include "config.h"

//readline函数实现
ssize_t readline(int fd,char *vptr,size_t maxlen)
{
    ssize_t n,rc;
    char c,*ptr;
    ptr = vptr;
    for(n = 1;n < maxlen;n++){
        if((rc = read(fd,&c,1)) == 1){
            *ptr++ = c;
            if(c == '\n'){
                break;
            }
        }else if(rc == 0){
            *ptr = 0;
            return(n - 1);
        }else {
            return(-1);
        }
    }
    *ptr = 0;
    return(n);
}
int main(int argc,char **argv)
{
    //声明套接字和链接服务器地址
    int sockfd;
    struct sockaddr_in servaddr;

    //判断是否为合法的输入
    if(argc != 2){
        perror("usage :tcpcli<IPaddress>");
        exit(1);
    }
    
    //(1)创建套接字
    if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
        perror("socket error");
        exit(1);
    }
    //(2)设置链接服务器地址结构
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr) < 0){
        printf("inet_pton error for %s\n",argv[1]);
        exit(1);
    }
    //(3)发送链接服务器请求
    if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0){
        perror("connect error");
        exit(1);
    }
    //(4)消息处理
    char sendline[MAX_LINE],recvline[MAX_LINE];
    while(fgets(sendline,MAX_LINE,stdin) != NULL){
        write(sockfd,sendline,strlen(sendline));

        if(readline(sockfd,recvline,MAX_LINE) == 0){
            perror("server terminated prematurely");
            exit(1);
        }
        if(fputs(recvline,stdout) == EOF){
            perror("fputs error");
            exit(1);
        }
    }
    //(5)关闭套接字
    close(sockfd);
}
执行结果:


  其实现的功能就是客户端发送什么,服务器就会回射回来什么。。。看似程序没什么问题,但其存在一个很大隐患就是僵尸进程,产生僵尸进程的最主要的原因就是:在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他, 那么他将变成一个僵尸进程。 但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程, 因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init 来接管他,成为他的父进程……

僵尸进程的危害

由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 那么会不会因为父进程太忙来不及wait子进程,或者说不知道 子进程什么时候结束,而丢失子进程结束时的状态信息呢? 不会。因为UNⅨ提供了一种机制可以保证只要 父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候, 内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到 父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生 僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
僵尸进程的避免
⒈父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起。
⒉ 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后, 父进程会收到该信号,可以在handler中调用wait回收。
⒊ 如果 父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN) 通知 内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。
⒋ 还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收 还要自己做。(上述的文字摘自百度百科)
下面给出一个不会产生僵尸进程的示例:
config.h:
#ifndef _CONFIG_H_
#define _CONFIG_H_

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

#define MAX_LINE 4096
#define SERV_PORT 9879
#define LISTENQ  6666
#define SA struct sockaddr
#define BACKLOG 10
typedef void Sigfunc(int);

struct args{
    long arg1;
    long arg2;
};
struct result{
    long sum;
};
#endif

config.c:
#include "config.h"

void sig_chld(int signo)
{
    pid_t pid;
    int stat;
    
    while((pid = waitpid(-1,&stat,WNOHANG)) > 0){
        printf("child %d terminated\n",pid);
    }
    return;
}
Sigfunc *Signal(int signo,Sigfunc *func)
{
    struct sigaction act,oact;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if(signo == SIGALRM){
        #ifdef SA_INTERRUPT
            act.sa_flags |= SA_INTERRUPT;
        #endif
    }else{
        #ifdef SA_RESTART
            act.sa_flags |= SA_RESTART;
        #endif
    }
    if(sigaction(signo,&act,&oact) < 0){
        return(SIG_ERR);
    }
    return(oact.sa_handler);
}
ssize_t readn(int fd,void *vptr,size_t n)
{
    size_t nleft;
    ssize_t nread;
    char *ptr;
    ptr = vptr;
    nleft = n;
    while(nleft > 0){
        if((nread = read(fd,ptr,nleft)) < 0){
            if(errno == EINTR){
                nread = 0;
            }else{
                return(-1);
            }
        }else if(nread == 0){
            break;
        }
        nleft -= nread;
        ptr += nread;
    }
    return(n - nleft);
}
void str_echo(int sockfd)
{
    ssize_t n;
    struct args args;
    struct result result;
    
    for(;;){
        if((n = readn(sockfd,&args,sizeof(args))) == 0){
            return;
        }
        //result.sum = args.arg1 ;
        if(write(sockfd,&result,sizeof(result)) < 0){
            perror("write error");
            exit(1);
        }
    }
}
void str_cli(FILE *fp,int sockfd)
{
    char sendline[MAX_LINE];
    struct args args;
    struct result result;
    while(fgets(sendline,MAX_LINE,fp) != NULL){
        if(sscanf(sendline,"%ld%ld",&args.arg1,&args.arg2) != 2){
            printf("invalid input:%s",sendline);
            continue;
        }
        if(write(sockfd,&args,sizeof(args)) < 0){
            perror("write error");
            exit(1);
        }
        if(readn(sockfd,&result,sizeof(result)) == 0){
            perror("str_cli:server terminated prematurely");
            exit(1);
        }
        
        result.sum = args.arg1 + args.arg2;
        printf("%ld\n",result.sum);
    }
}

服务器端:
#include "config.h"

int main(int argc,char **argv)
{
    //定义监听套接字和连接套接字
    int listenfd,connfd;
    //定义子进程pid
    pid_t childpid;
    //套接字地址结构的长度
    socklen_t clilen;
    //ipv4套接字地址结构
    struct sockaddr_in cliaddr,servaddr;
    //信号处理函数
    void sig_chld(int);

    //创建套接字
    if((listenfd = socket(AF_INET,SOCK_STREAM,0)) < 0){
        perror("socket error");
        exit(1);
    }

    //指定服务器ip和端口
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    
    //绑定协议地址和套接字
    if((bind(listenfd,(SA *)&servaddr,sizeof(servaddr))) < 0){
        perror("bind error");
        exit(1);
    }
    
    //监听套接字
    if(listen(listenfd,LISTENQ) < 0){
        perror("listen error");
        exit(1);
    }
    
    Signal(SIGCHLD,sig_chld);

    for(;;){
        clilen = sizeof(cliaddr);
        if((connfd = accept(listenfd,(SA *)&cliaddr,&clilen)) < 0){
            if(errno = EINTR){
                continue;
            }else{
                perror("accept error");
                exit(1);
            }
        }
        //创建子进程
        if((childpid = fork()) == 0){
            //关闭监听套接字
            close(listenfd);
            str_echo(connfd);
            exit(0);
        }
        //父进程关闭连接
        close(connfd);
    }
}

客户端:
#include "config.h"

int main(int argc,char **argv)
{
    int sockfd;
    struct sockaddr_in servaddr;

    if(argc != 2){
        printf("usage:tcpcli<IPaddress>");
        exit(1);
    }

    if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0){
        perror("socket error");
        exit(1);
    }

    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr) < 0){
        printf("inet_pton error for %s\n",argv[1]);
        exit(1);
    }
    if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0){
        perror("connect error");
        exit(1);
    }
    str_cli(stdin,sockfd);
    exit(0);
}

执行结果:

上述示例的功能就是完成客户端给服务器发送两个数,服务器进行求和,同时也没有僵尸进程的出现!!!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值