(二)socket编程(二)

目录

socket编程(四)

流协议与粘包

粘包产生的原因

粘包处理方案--本质上是要在应用层维护消息与消息的边界

readn writen

socket编程(五)

read、write与recv、send

readline实现

用readline实现回射客户/服务器

getsockname、getpeername

gethostname、gethostbyname、gethostbyaddr

socket编程(六)

TCP是个流协议

僵进程与SIGCHLD信号

什么是僵尸进程?

僵尸进程的目的?

如何避免僵尸进程?

僵尸进程处理办法


socket编程(四)

流协议与粘包

TCP/IP协议是一种流协议,流协议是字节流,只有开始和结束,包与包之间没有边界,所以容易产生粘包,但是不会丢包。
UDP/IP协议是数据报,有边界,不存在粘包,但是可能丢包。
粘包的几种状态

假设主机A发送了两个数据包M1和M2给主机B,由于主机B一次接收的字节数是不确定的,故可能存在上图的4种情况,

1、分两次接收两个数据包,一次一个,没有粘包问题;

2、一次接收了两个数据包,存在粘包问题;

3、第一个接收了M1和M2的一部分,第二次接收M2的另一部分,存在粘包问题;

4、第一次接收了M1的一部分,第二次接收M1的另一部分和M2,存在粘包问题;

当然实际的情况可能不止以上4种,可以得知的是在互联网上通信很容易造成粘包问题。

 

粘包产生的原因

1.SQ_SNDBUF套接字本身有缓冲区(发送缓冲区,接收缓冲区)
2.tcp传送的网络数据最大值MSS大小限制
3.链路层也有MTU(最大传输单元)大小限制,如果数据包大于>MTU要在IP层进行分片,导致消息分割。
4.tcp的流量控制和拥塞控制,也可能导致粘包
5.tacp延迟发送机制等等
结论:TCP/IP协议,在传输层没有处理粘包问题,必须由程序员处理

粘包处理方案--本质上是要在应用层维护消息与消息的边界

1.定包长
2.包尾加\r\n(比如ftp协议)
3.包头加上包体长度
4.更复杂的应用层协议

readn writen

//粘包解决方案--包头加上包体长度
//服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

typedef struct _packet
{
    int len; //定义包体长度
    char buf[1024]; //定义包体
} Packet;

/*
 * fd:文件描述符
 * buf:数据缓存区
 * count:读取字符数
 * */
ssize_t readn(int fd, const void *buf, ssize_t count)
{
    //定义临时指针变量
    char *pbuf = (char *)buf;
    //定义每次已读数据
    ssize_t nread = 0;
    //定义剩余数据
    ssize_t lread = count;
    while (lread > 0)
    {
        nread = read(fd, pbuf, lread);
        /*
         * 情况分析:假设b缓冲区buf足够大
         * 如果nread==count,说明数据正好被读完
         * nread<count,说明数据没有被读完,这种情况就是由于粘包产生的
         * socket只接收了数据的一部分,TCP/IP协议不可能出现丢包情况
         * nread==0,说明对方关闭文件描述符
         * nread==-1,说明read函数报错
         * nread>count,这种情况不可能存在
         * */
        if (nread == -1)
        {
            //read()属于可中断睡眠函数,需要做信号处理
            if (errno == EINTR)
                continue;
            perror("read() err");
            return -1;
        } else if (nread == 0)
        {
            printf("client is closed !\n");
            //返回已经读取的字节数
            return count - lread;
        }
        //重新获取 剩余的 需要读取的 字节数
        lread = lread - nread;
        //指针后移
        pbuf = pbuf + nread;
    }
    return count;
}

/* fd:文件描述符
 * buf:数据缓存区
 * count:读取字符数
 * */
ssize_t writen(int fd, const void *buf, ssize_t count)
{
    //定义临时指针变量
    char *pbuf = (char *)buf;
    //每次写入字节数
    ssize_t nwrite = 0;
    //剩余未写字节数
    ssize_t lwrite = count;
    while (lwrite > 0)
    {
        nwrite = write(fd, pbuf, lwrite);
        if (nwrite == -1)
        {
            if (errno == EINTR)
                continue;
            perror("write() err");
            return -1;
        } else if (nwrite == 0)
        {
            printf("client is closed !\n");
            //对方关闭文件描述符,返回已经写完的字节数
            return count - lwrite;
        }
        lwrite -= nwrite;
        pbuf += nwrite;
    }
    return count;
}

int main(int arg, char *args[])
{
    //create socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1)
    {
        perror("socket() err");
        return -1;
    }
    //reuseaddr
    int optval = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
            == -1)
    {
        perror("setsockopt() err");
        return -1;
    }
    //bind
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (bind(listenfd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
    {
        perror("bind() err");
        return -1;
    }
    //listen
    if(listen(listenfd,SOMAXCONN)==-1)
    {
        perror("listen() err");
        return -1;
    }
    //accept
    struct sockaddr_in peeraddr;
    socklen_t peerlen = sizeof(peeraddr);
    int conn = accept(listenfd, (struct sockaddr *)&peeraddr,&peerlen);
    if (conn == -1)
    {
        perror("accept() err");
        return -1;
    }
    Packet _packet;
    while (1)
    {
        memset(&_packet, 0, sizeof(_packet));
        //获取报文自定义包头
        int rc = readn(conn, &_packet.len, 4);
        if (rc == -1)
        {
            exit(0);
        } else if (rc < 4)
        {
            exit(0);
        }
        //把网络字节序转化成本地字节序
        int n = ntohl(_packet.len);
        //获取报文自定义包体
        rc = readn(conn, _packet.buf, n);
        if (rc == -1)
        {
            exit(0);
        } else if (rc < n)
        {
            exit(0);
        }
        //打印报文数据
        fputs(_packet.buf, stdout);
        //将原来的报文数据发送回去
        printf("发送报文的长度%d\n", 4 + n);
        rc = writen(conn, &_packet, 4 + n);
        if (rc == -1)
        {
            exit(0);
        } else if (rc < 4 + n)
        {
            exit(0);
        }
    }
    return 0;
}
//粘包解决方案--包头加上包体长度
//客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

typedef struct _packet
{
    int len; //定义包体长度
    char buf[1024]; //定义包体
} Packet;

/*
 * fd:文件描述符
 * buf:数据缓存区
 * count:读取字符数
 * */
ssize_t readn(int fd, const void *buf, ssize_t count)
{
    //定义临时指针变量
    char *pbuf = (char *)buf;
    //定义每次已读数据
    ssize_t nread = 0;
    //定义剩余数据
    ssize_t lread = count;
    while (lread > 0)
    {
        nread = read(fd, pbuf, lread);
        /*
         * 情况分析:假设b缓冲区buf足够大
         * 如果nread==count,说明数据正好被读完
         * nread<count,说明数据没有被读完,这种情况就是由于粘包产生的
         * socket只接收了数据的一部分,TCP/IP协议不可能出现丢包情况
         * nread==0,说明对方关闭文件描述符
         * nread==-1,说明read函数报错
         * nread>count,这种情况不可能存在
         * */
        if (nread == -1)
        {
            //read()属于可中断睡眠函数,需要做信号处理
            if (errno == EINTR)
                continue;
            perror("read() err");
            return -1;
        } else if (nread == 0)
        {
            printf("client is closed !\n");
            //返回已经读取的字节数
            return count - lread;
        }
        //重新获取 剩余的 需要读取的 字节数
        lread = lread - nread;
        //指针后移
        pbuf = pbuf + nread;
    }
    return count;
}

/* fd:文件描述符
 * buf:数据缓存区
 * count:读取字符数
 * */
ssize_t writen(int fd, const void *buf, ssize_t count)
{
    //定义临时指针变量
    char *pbuf = (char *)buf;
    //每次写入字节数
    ssize_t nwrite = 0;
    //剩余未写字节数
    ssize_t lwrite = count;
    while (lwrite > 0)
    {
        nwrite = write(fd, pbuf, lwrite);
        if (nwrite == -1)
        {
            if (errno == EINTR)
                continue;
            perror("write() err");
            return -1;
        } else if (nwrite == 0)
        {
            printf("client is closed !\n");
            //对方关闭文件描述符,返回已经写完的字节数
            return count - lwrite;
        }
        lwrite -= nwrite;
        pbuf += nwrite;
    }
    return count;
}

int main(int arg, char *args[])
{
    //create socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        perror("socket() err");
        return -1;
    }
    //connect
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
    {
        perror("connect() err");
        return -1;
    }
    int rc = 0, numx = 0;
    Packet _packet;
    memset(&_packet, 0, sizeof(_packet));
    while (fgets(_packet.buf, sizeof(_packet.buf), stdin) != NULL)
    {
        //发送数据
        numx = strlen(_packet.buf);
        //将本地字节转化成网络字节序
        _packet.len = htonl(numx);
        rc = writen(sockfd, &_packet, 4 + numx);
        if (rc == -1)
        {
            return -1;
        } else if (rc < 4 + numx)
        {
            return -1;
        }
        //接收数据
        memset(&_packet, 0, sizeof(_packet));
        //获取包头
        rc = readn(sockfd, &_packet.len, 4);
        if (rc == -1)
        {
            return -1;
        } else if (rc < 4)
        {
            return -1;
        }
        //将网络字节转化成本地字节
        numx = ntohl(_packet.len);
        //printf("接收数据的大小是%d\n",numx);
        //获取包体
        rc = readn(sockfd, &_packet.buf, numx);
        if (rc == -1)
        {
            return -1;
        } else if (rc < numx)
        {
            return -1;
        }
        //打印包体
        fputs(_packet.buf,stdout);
        memset(&_packet, 0, sizeof(_packet));
    }
    return 0;
}

 

 

socket编程(五)

read、write与recv、send

recv和send函数提供了和read和write差不多的功能。但是他们提供了第四个参数来控制读写操作.

函数原形:

int recv(int sockfd,void *buf,int len,int flags)

int send(int sockfd,void *buf,int len,int flags)

所属头文件:

#include <sys/types.h> #include <sys/socket.h>

参数说明:

前面的三个参数和read,write相同,第四个参数能够是0或是以下的组合:

______________________________________________________________

| MSG_DONTROUTE | 不查找路由表 |

| MSG_OOB       | 接受或发送带外数据 |

| MSG_PEEK      | 查看数据,并不从系统缓冲区移走数据 |

| MSG_WAITALL   | 等待任何数据 |

|————————————————————–|


MSG_DONTROUTE:
是send函数使用的标志.这个标志告诉IP协议.目的主机在本地网络上面,没有必要查找路由表.这个标志一般用网络诊断和路由程式里面.

MSG_OOB:
表示能够接收和发送带外的数据.关于带外数据我们以后会解释的.

MSG_PEEK:
是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清楚系统缓冲区的内容.
这样下次读的时候,仍然是相同的内容.一般在有多个进程读写数据时能够使用这个标志.

MSG_WAITALL
是recv函数的使用标志,表示等到任何的信息到达时才返回.
使用这个标志的时候recv回一直阻塞,直到指定的条件满足,或是发生了错误.1

 

 

readline实现

实现的功能:

一次只能读取一行,客户端输入之后,一回车,马上字符串传到服务器端并显示在终端,然后服务器端将字符串又传回给客户端。

服务器端可以接收多个客户端的连接请求,并fork一个子进程来进行服务。

(1)封装一个只能访问套接字描述符的readline函数

(2)服务器端启动SO_REUSEADDR套接字选项,以便服务器端不必等待TIME_WAIT状态

用readline实现回射客户/服务器

服务器端代码:

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

#define ERR_EXIT(m) \
        do \
        { \
            perror(m); \
            exit(EXIT_FAILURE);\
        }while(0)

void do_service(int conn)
{
    char recvbuf[1024];
    while(1)
    {
        memset(recvbuf,0,sizeof(recvbuf));
        int ret=readline(conn,recvbuf,sizeof(recvbuf));
        if(-1==ret)
            ERR_EXIT("readline in do_servece");
        else if(0==ret)
        {
            printf("client close\n");
            break;
        }
        fputs(recvbuf,stdout);
        writen(conn,recvbuf,strlen(recvbuf));
    }
}
            
ssize_t readn(int fd,void *buf,size_t count)
{
    size_t nleft=count;
    ssize_t nread;
    char*bufp=(char*)buf;
    while(nleft>0)
    {
        if((nread=read(fd,bufp,nleft))<0)
        {
            if(errno==EINTR)
                continue;
            return -1;
        }
        else if(nread==0)
            return count-nleft;
        bufp+=nread;
        nleft-=nread;
    }
    return count;
}

ssize_t writen(int fd,const void*buf,size_t count)
{
    size_t nleft=count;
    ssize_t nwritten;
    char*bufp=(char*)buf;
    while(nleft>0)
    {
        if((nwritten=write(fd,bufp,nleft))<0)
        {
            if(errno==EINTR)
                continue;
            return -1;
        }
        else if(nwritten==0)
            continue;
        bufp+=nwritten;
        nleft-=nwritten;
    }
    return count;
}

ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
    while(1)
    {
        int ret=recv(sockfd,buf,len,MSG_PEEK);//窥看,不清除缓存中的数据
        if(-1==ret&&EINTR==errno)
            continue;
        return ret;
    }
}

ssize_t readline(int sockfd,void* buf,size_t maxline)
{
    int ret;
    int nread;
    char* bufp=(char*)buf;
    int nleft=maxline;
    while(1)
    {
        ret=recv_peek(sockfd,bufp,nleft);
        if(ret<0)
            return ret;
        else if(0==ret)
            return ret;
        nread=ret;
        int i;
        for(i=0;i<nread;i++)//读取到'\n'时就应该结束
        {
            if(bufp[i]=='\n')
            {
                ret=readn(sockfd,bufp,i+1);
                if(ret!=i+1)
                    exit(EXIT_FAILURE);
                return ret;
            }
        }
        //执行到了这儿,说明没有读取到'\n'
        if(nread>nleft)
            exit(EXIT_FAILURE);
        nleft-=nread;
        ret=readn(sockfd,bufp,nread);//清空缓存中的数据,因为之前是窥视,并没有清空缓存
        if(ret!=nread)
            exit(EXIT_FAILURE);
        bufp+=nread;//指针后移,接着去执行while循环,确保数据追加到原来已读取数据的末尾
    }
    return -1;
}        
int main(void)
{
    int listenfd;
    if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
        ERR_EXIT("socket");
    
    struct sockaddr_in servaddr;
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(5188);
    servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    //套接字选项的设置一定要在bind之前
    int on=1;
    if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
            ERR_EXIT("setsockopt");
    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
        ERR_EXIT("bind");
    if(listen(listenfd,SOMAXCONN)<0)
        ERR_EXIT("listen");
    struct sockaddr_in peeraddr;
    socklen_t peerlen=sizeof(peeraddr);
    int conn;
    pid_t pid;
    while(1)
    {
        if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
            ERR_EXIT("accept");
        printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
        pid=fork();
        if(-1==pid)
            ERR_EXIT("fork");
        if(0==pid)//子进程
        {
            close(listenfd);
            do_service(conn);
            exit(EXIT_SUCCESS);
        }
        else close(conn);//父进程
    }
    
    
    return 0;
}

 客户端代码:

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

#define ERR_EXIT(m) \
        do \
        { \
            perror(m); \
            exit(EXIT_FAILURE);\
        }while(0)
            
ssize_t readn(int fd,void *buf,size_t count)
{
    size_t nleft=count;
    ssize_t nread;
    char*bufp=(char*)buf;
    while(nleft>0)
    {
        if((nread=read(fd,bufp,nleft))<0)
        {
            if(errno==EINTR)
                continue;
            return -1;
        }
        else if(nread==0)
            return count-nleft;
        bufp+=nread;
        nleft-=nread;
    }
    return count;
}

ssize_t writen(int fd,const void*buf,size_t count)
{
    size_t nleft=count;
    ssize_t nwritten;
    char*bufp=(char*)buf;
    while(nleft>0)
    {
        if((nwritten=write(fd,bufp,nleft))<0)
        {
            if(errno==EINTR)
                continue;
            return -1;
        }
        else if(nwritten==0)
            continue;
        bufp+=nwritten;
        nleft-=nwritten;
    }
    return count;
}

ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
    while(1)
    {
        int ret=recv(sockfd,buf,len,MSG_PEEK);//窥看,不清除缓存中的数据
        if(-1==ret&&EINTR==errno)
            continue;
        return ret;
    }
}

ssize_t readline(int sockfd,void* buf,size_t maxline)
{
    int ret;
    int nread;
    char* bufp=(char*)buf;
    int nleft=maxline;
    while(1)
    {
        ret=recv_peek(sockfd,bufp,nleft);
        if(ret<0)
            return ret;
        else if(0==ret)
            return ret;
        nread=ret;
        int i;
        for(i=0;i<nread;i++)//读取到'\n'时就应该结束
        {
            if(bufp[i]=='\n')
            {
                ret=readn(sockfd,bufp,i+1);
                if(ret!=i+1)
                    exit(EXIT_FAILURE);
                return ret;
            }
        }
        //执行到了这儿,说明没有读取到'\n'
        if(nread>nleft)
            exit(EXIT_FAILURE);
        nleft-=nread;
        ret=readn(sockfd,bufp,nread);//清空缓存中的数据,因为之前是窥视,并没有清空缓存
        if(ret!=nread)
            exit(EXIT_FAILURE);
        bufp+=nread;//指针后移,接着去执行while循环,确保数据追加到原来已读取数据的末尾
    }
    return -1;
}
int main(void)
{
    int sock;
    if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
        ERR_EXIT("socket");
    struct sockaddr_in servaddr;
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(5188);
    servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//inet_addr将ip地址字符串转为数字形式,且已经是网络字节序    
    
    if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)//
        ERR_EXIT("connect");
        
    char sendbuf[1024]={0};
    char recvbuf[1024]={0};
    while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
    {
        writen(sock,sendbuf,strlen(sendbuf));
        
        int ret=readline(sock,recvbuf,sizeof(recvbuf));
        if(-1==ret)
            ERR_EXIT("readline");
        else if(0==ret)
        {
            printf("client?server close\n");
            break;
        }
        fputs(recvbuf,stdout);
    }
    
    return 0;
}

getsockname、getpeername

函数原形:

int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);  
  
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);  

函数功能:

getsockname函数用于获取与某个套接字关联的本地协议地址 
getpeername函数用于获取与某个套接字关联的外地协议地址

所属头文件:

#include<sys/socket.h>  

返回值

对于这两个函数,如果函数调用成功,则返回0,如果调用出错,则返回-1。

参数说明:

使用这两个函数,我们可以通过套接字描述符来获取自己的IP地址和连接对端的IP地址,
如在未调用bind函数的TCP客户端程序上,可以通过调用getsockname()函数获取由内核赋予该连接的本地IP地址和本地端口号,
还可以在TCP的服务器端accept成功后,通过getpeername()函数来获取当前连接的客户端的IP地址和端口号。

 

gethostname、gethostbyname、gethostbyaddr

函数原形:

int              gethostname(char *name, size_t len);
struct hostent*  gethostbyname(const char *name);
struct hostent * gethostbyaddr(const char * addr, socklen_t len, int family);

函数功能:

gethostname() : 返回本地主机的标准主机名。
gethostbyname(): 通过主机名称获取主机的信息
gethostbyaddr(): 通过一个IPv4的地址来获取主机信息

所属头文件:

#include <netdb.h> #include <unistd.h>

返回值

gethostname()  : 函数成功,则返回0。如果发生错误则返回-1。错误号存放在外部变量errno中。
gethostbyname(): 返回hostent结构体类型指针 
gethostbyaddr(): 返回hostent结构体类型指针

参数说明:

gethostname() : 
接收缓冲区name,其长度必须为len字节或是更长,存获得的主机名。
接收缓冲区name的最大长度


gethostbyname(): 
传入值是域名或者主机名,例如"www.google.cn"等等。传出值,是一个hostent的结构。


gethostbyaddr(): 
addr:指向网络字节顺序地址的指针。
len: 地址的长度,在AF_INET类型地址中为4。
type:地址类型,应为AF_INE

 

socket编程(六)

TCP是个流协议

这就意味着数据是以字节流的形式传递给接收者的,没有固有的”报文”或”报文边界”的概念。

从这方面来说,读取TCP数据就像从串行端口读取数据一样–无法预先得知在一次指定的读调用中会返回多少字

粘包问题的解决方法是在应用层维护消息边界

僵进程与SIGCHLD信号

什么是僵尸进程

一个进程执行了exit系统调用退出时会向父进程发送SIGCHLD信号,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态,如进程ID、终止状态等等)的进程。

僵尸进程的目的?

设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。

如何避免僵尸进程?

  1. 通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
  2. 父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞waitpid可以通过传递WNOHANG使父进程不阻塞立即返回
  3. 如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
  4. 通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。

第一种方法忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。

如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。

 

僵尸进程处理办法

wait

函数原形:

pid_t wait(int *status);

所属头文件:

#include <sys/types.h> 
#include <sys/wait.h>

返回值

成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHIL

说明:

进程一旦调用了wait,就立即阻塞自己


wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。
返回的是子进程的PID,它通常是结束的子进程
状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit语句的退出码。
如果status不是一个空指针,状态信息将被写入它指向的位置


 

waitpid

函数原形:

pid_t waitpid(pid_t pid, int *status, int options);

所属头文件:

#include <sys/types.h> 
#include <sys/wait.h>

返回值

返回值:如果成功返回等待子进程的ID,失败返回-1

说明:

status:如果不是空,会把状态信息写到它指向的位置,与wait一样


options:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起

----------------------------------------------------------------------------
|-对于waitpid的p i d参数的解释与其值有关:
|-
|-pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。
|-
|-pid > 0 等待其进程I D与p i d相等的子进程。
|-
|-pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用者进程同在一个组的进程。
|-
|-pid < -1 等待其组I D等于p i d的绝对值的任一子进程
----------------------------------------------------------------------------

 

wait与waitpid区别:

  • 在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
  • waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
  • 实际上wait函数是waitpid函数的一个特例。waitpid(-1, &status, 0);

image

当客户终止时,所有打开的描述符字由内核自动关闭,且所有5个连接基本在同一时刻终止。这就引发了5个FIN,每个连接一个,它们反过来使服务器的5个子进程基本在同一时刻终止。这又导致差不多在同一时刻递交5个SIGCHLD信号给父进程。

如果我们调用函数wait来处理已终止的子进程,那么只会捕获到一个SIGCHLD信号。也就是说,其他的4个子进程仍然作为僵死进程存在着。

所以,建立一个信号处理函数并在其中调用wait并不足以防止出现僵死进程。本问题在于:所有5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次,因为UNIX信号一般是不排队的。

正确的解决办法是调用waitpid而不是wait。如下所示给出了正确处理SIGCHLD的sig_chld函数的版本。这个版本管用的原因在于:我们在一个循环内调用waitpid,以获取所有已终止子进程的状态。我们必须指定WNOHANG选项,它告知waitpid在有尚未终止的子进程在运行时不要阻塞。我们不能在循环内调用wait,因为没有办法防止wait在尚有未终止的子进程在运行时阻塞。

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值