目录
gethostname、gethostbyname、gethostbyaddr
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、终止状态等等)的进程。
僵尸进程的目的?
设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。
如何避免僵尸进程?
- 通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
- 父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
- 如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
- 通过两次调用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);
当客户终止时,所有打开的描述符字由内核自动关闭,且所有5个连接基本在同一时刻终止。这就引发了5个FIN,每个连接一个,它们反过来使服务器的5个子进程基本在同一时刻终止。这又导致差不多在同一时刻递交5个SIGCHLD信号给父进程。
如果我们调用函数wait来处理已终止的子进程,那么只会捕获到一个SIGCHLD信号。也就是说,其他的4个子进程仍然作为僵死进程存在着。
所以,建立一个信号处理函数并在其中调用wait并不足以防止出现僵死进程。本问题在于:所有5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次,因为UNIX信号一般是不排队的。
正确的解决办法是调用waitpid而不是wait。如下所示给出了正确处理SIGCHLD的sig_chld函数的版本。这个版本管用的原因在于:我们在一个循环内调用waitpid,以获取所有已终止子进程的状态。我们必须指定WNOHANG选项,它告知waitpid在有尚未终止的子进程在运行时不要阻塞。我们不能在循环内调用wait,因为没有办法防止wait在尚有未终止的子进程在运行时阻塞。