千家信息网

Linux网络编程wait()和waitpid()的详细讲解

发表于:2025-12-01 作者:千家信息网编辑
千家信息网最后更新 2025年12月01日,本篇内容介绍了"Linux网络编程wait()和waitpid()的详细讲解"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔
千家信息网最后更新 2025年12月01日Linux网络编程wait()和waitpid()的详细讲解

本篇内容介绍了"Linux网络编程wait()和waitpid()的详细讲解"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

客户端断开连接后,服务器端存在大量僵尸进程。这是由于服务器子进程终止后,发送SIGCHLD信号给父进程,而父进程默认忽略了该信号。为避免僵尸进程的产生,无论我们什么时候创建子进程时,主进程都需要等待子进程返回,以便对子进程进行清理。为此,我们在服务器程序中添加SIGCHLD信号处理函数。

代码如下:


#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 1113
#define LISTENQ 32
#define MAXLINE 1024
/***连接处理函数***/
void str_echo(int fd);
void
sig_chld(int signo)
{
pid_t pid;
int stat;
pid = wait(&stat);//获取子进程进程号
printf("child %d terminated\n", pid);
return;
}
int
main(int argc, char *argv[]){
int listenfd,connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in servaddr;
struct sockaddr_in cliaddr;
//struct sockaddr_in servaddr;
//struct sockaddr_in cliaddr;
if((listenfd = socket(AF_INET, SOCK_STREAM,0))==-1){
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
exit(1);
}
/* 服务器端填充 sockaddr结构*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
signal(SIGCHLD,sig_chld);//处理SIGCHLD信号
/* 捆绑listenfd描述符 */
if(bind(listenfd,(struct sockaddr*)(&servaddr),sizeof(struct sockaddr))==-1){
fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
exit(1);
}
/* 监听listenfd描述符*/
if(listen(listenfd,5)==-1){
fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
exit(1);
}
for ( ; ; ) {
clilen = sizeof(cliaddr);
/* 服务器阻塞,直到客户程序建立连接 */
if((connfd=accept(listenfd,(struct sockaddr*)(&cliaddr),&clilen))<0){
/*当一个子进程终止时,执行信号处理函数sig_chld,
而该函数返回时,accept系统调用可能返回一个EINTR错误,
有些内核会自动重启被中断的系统调用,为便于移植,将考虑对EINTR的处理*/
if(errno==EINTR)
continue;
fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
exit(1);
}
//有客户端建立了连接后
if ( (childpid = fork()) == 0) { /*子进程*/
close(listenfd); /* 关闭监听套接字*/
str_echo(connfd); /*处理该客户端的请求*/
exit (0);
}
close(connfd);/*父进程关闭连接套接字,继续等待其他连接的到来*/
}
}
void str_echo(int sockfd){
ssize_t n;
char buf[MAXLINE];
again:
while ( (n = read(sockfd, buf, MAXLINE)) > 0)
write(sockfd, buf, n);
if (n < 0 && errno == EINTR)//被中断,重入
goto again;
else if (n < 0){//出错
fprintf(stderr,"read error:%s\n\a",strerror(errno));
exit(1);
}
}


修改代码后,当客户端断开连接后,服务器端父进程收到子进程的SIGCHLD信号后,会执行sig_chld函数,对子进程进行了清理,便不会再出现僵尸进程。此时,一个客户端主动断开连接后,服务器端会输出类似如下信息:
child 12306 terminated
wait和waitpid
上述程序中sig_chld函数,我们使用了wait()来清除终止的子进程。还有一个类似的函数wait_pid。我们先来看看这两个函数原型:
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
官方描述:All of these system calls are used to wait for state changes in a child of the calling process, and obtain information about the child whose state has changed. A state change is considered to be: the child ter minated; the child was stopped by a signal; or the child was resumed by a signal. In the case of a terminated child, performing a wait allows the system to release the resources associated with the child; if a wait is not performed, then the terminated child remains in a "zombie" state (see NOTES below).
关于wait和waitpid两者的区别与联系:
The wait() system call suspends execution of the calling process until one of its children terminates. The call wait(&status) is equivalent to:
waitpid(-1, &status, 0);
The waitpid() system call suspends execution of the calling process until a child specified by pid argument has changed state. By default, waitpid() waits only for terminated children, but this behavior is modifiable via the options argument, as described below.
  也就是说,wait()系统调用会挂起调用进程,直到它的任意一个子进程终止。调用wait(&status)的效果跟调用waitpid(-1, &status, 0)的效果是一样一样的。
  waitpid()会挂起调用进程,直到参数pid指定的进程状态改变,默认情况下,waitpid() 只等待子进程的终止状态。如果需要,可以通过设置options的值,来处理非终止状态的情况。比如:
The value of options is an OR of zero or more of the following constants:
WNOHANG return immediately if no child has exited.
WUNTRACED also return if a child has stopped (but not traced via ptrace(2)). Status for traced children which have stopped is provided even if this option is not specified.
WCONTINUED (since Linux 2.6.10)also return if a stopped child has been resumed by delivery of SIGCONT.
等等一下非终止状态。
现在来通过实例看看wait()和waitpid()的区别。
通过修改客户端程序,在客户端程序中一次性建立5个套接字连接到服务器,状态如下图所示(附代码):





代码如下:


#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 1113
#define MAXLINE 1024
void str_cli(FILE *fp, int sockfd);
int
main(int argc, char **argv)
{
int i,sockfd[5];
struct sockaddr_in servaddr;
if (argc != 2){
fprintf(stderr,"usage: tcpcli \n\a");
exit(0);
}
for(i=0;i<5;++i){//与服务器建立五个连接,以使得服务器创建5个子进程
if((sockfd[i]=socket(AF_INET,SOCK_STREAM,0))==-1){
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
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){
fprintf(stderr,"inet_pton Error:%s\a\n",strerror(errno));
exit(1);
}
/* 客户程序发起连接请求*/
if(connect(sockfd[i],(struct sockaddr *)(&servaddr),sizeof(struct sockaddr))==-1){
fprintf(stderr,"connect Error:%s\a\n",strerror(errno));
exit(1);
}
}
str_cli(stdin, sockfd[0]);/*仅用第一个套接字与服务器交互*/
exit(0);
}
void
str_cli(FILE *fp, int sockfd)
{
int nbytes=0;
char sendline[MAXLINE],recvline[MAXLINE];
while (fgets(sendline, MAXLINE, fp) != NULL){//从标准输入中读取一行
write(sockfd, sendline, strlen(sendline));//将该行发送给服务器
if ((nbytes=read(sockfd, recvline, MAXLINE)) == 0){//从sockfd读取从服务器发来的数据
fprintf(stderr,"str_cli: server terminated prematurely\n");
exit(1);
}
recvline[nbytes]='\0';
fputs(recvline, stdout);
}
}


当客户终止时,所以打开的描述子均由内核自动关闭,因此5个连接基本在同一时刻发生,相当于同时引发了5个FIN发往服务器,这会导致5个服务器子进程基本在同一时刻终止,从而导致5个SIGCHLD信号几乎同时递送给服务器父进程,示意图如下所示:

也就是说,几乎在同一时刻,递送5个SIGCHLD信号给父进程,这又会僵尸进程进程的出现。因为unix一般不对信号进行排队,这就导致了5个SIGCHLD递交上去,只执行了一次sig_chld函数,剩下四个子进程便成为了僵尸进程。对于这种情况,正确的做法是调用waitpid(),而不是wait()。
因此,我们最后的服务器端代码中的信号处理函数做一点小改动,改成如下:

代码如下:


void
sig_chld(int signo)
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
return;
}


至此,我们解决了网络编程中可能遇到的三类情况:
1.当派生子进程时,必须捕获SIGCHLD信号。代码片段:signal(SIGCHLD,sig_chld);
2.当捕获信号时,必须处理被中断的系统调用。代码片段:if(errno==EINTR) continue;
3.SIGCHLD信号处理函数必须编写正确,以防出现僵尸进程。代码片段:while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)

"Linux网络编程wait()和waitpid()的详细讲解"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

进程 服务 服务器 信号 函数 客户 处理 代码 程序 僵尸 客户端 情况 状态 个子 信号处理 套接字 系统 网络 网络编程 编程 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 西集镇网络安全产业园 excel合并数据库 网络安全网络攻防论文摘要 长宁区立体化软件开发定制价格 数据库可以设置列值为null吗 宣城企业软件开发定制 泉州信息工程学院图书馆数据库 51单片机软件开发ide 直播教学类软件开发的目的 浦东新区公司软件开发包括哪些 杭州善班互联网科技有限公司 access数据库OLE和附件 网络安全实训室优势 网络安全问题日益重要英语 网络安全和信息化工作是当前 在线sql转数据库模型 海安市网络安全保护大队 如何加入网络安全员 c 向数据库写数据 开发app可以在阿里买服务器吗 六安中擎网络技术服务有限公司 5g网络安全有前景吗 数据库setfield 王者荣耀换服务器账号会没吗 新华互联网科技院 数据库比较符 查看当前数据库中有哪些表的语句 安卓软件开发传值 csgo进社区服务器会不会封号 设置服务器网络共享文件
0