(1) 分析:
多进程的并发服务器中,父进程一旦监听到客户端连接,就会创建相应的子进程来处理。如果用waipid(),父进程因为accept阻塞无法跳转。因此,防止僵尸进程,只能依靠信号,要进行信号改造,一有子进程退出就会产生DIGCHLD信号,改造DIGCHLD信号,使得父进程一收到信号就跳过来回收。
如果成功读到cnt,不应该用break,应该用close(client_fd)。因为整个大的while(1)已经属于子进程的内容 。子进程资源是完全copy了父进程一份,如果client退出的话就直接break,acceptclient_fd,就会出错。所以用return 0,直接退出子进程。
(2)代码
server.c
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<wait.h>
#include<netinet/in.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include <arpa/inet.h>
#include<signal.h>
int signal_quit_hander(int signo)
{
switch(signo)
{
case SIGCHLD:
printf("child process quit\n");
/*捕捉到SIGCHLD信号,父进程不阻塞但等待子进程退出,并回收释放资源*/
while(waitpid(-1,NULL,WNOHANG) > 0);
break;
}
}
int signal_setup()
{
struct sigaction act,oact;
act.sa_handler = (void *)signal_quit_hander; //新的信号处理方式
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_flags |= SA_RESTART;
if(sigaction(SIGCHLD,&act,&oact) < 0)
{
return -1;
}
return 0;
}
int main()
{
int fd;
pid_t pid;
int sockfd,client_fd;
int cnt;
struct sockaddr_in saddr,caddr;
socklen_t slen,clen;
char rdBuff[128] = {0};
sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sockfd < 0)
{
perror("socket failed\n");
exit(1);
}
printf("sockfd:%d\n",sockfd);
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8090);
saddr.sin_addr.s_addr = htons(INADDR_ANY);
slen = sizeof(saddr);
clen = sizeof(caddr);
bind(sockfd,(struct sockaddr *)&saddr,slen);
signal_setup();
listen(sockfd,100);
while(1)
{
printf("listen ...\n");
client_fd = accept(sockfd,(struct sockaddr *)&caddr,&clen);
printf("connect successfully\n");
printf("client_fd:%d\n",client_fd);
printf("cip:%s\n",inet_ntoa(caddr.sin_addr));
pid = fork();
if(pid < 0)
{
perror("fork failed\n");
exit(1);
}
else if(pid == 0)
{
close(sockfd);
while(1)
{
cnt = read(client_fd,rdBuff,128);
if(cnt < 0)
{
perror("read failed\n");
exit(2);
}
else if(cnt == 0)
{
printf("client offline...\n");
close(client_fd);
//break;
return 0;
}
else
{
printf("rd = %s\n",rdBuff);
write(client_fd,"ok read data end",18);
}
}
}
else
{
close(client_fd);
}
}
close(sockfd);
return 0;
}
~
client.c
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/msg.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<sys/stat.h>
int main()
{
int client_fd;
int i = 10;
int ret;
int sockfd,file_fd;
struct sockaddr_in saddr = {0};
socklen_t slen;
char rdBuff[128] = {0};
sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sockfd < 0)
{
perror("socket failed\n");
exit(1);
}
printf("sockfd:%d\n",sockfd);
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8090);
saddr.sin_addr.s_addr = inet_addr("192.168.56.133");
slen = sizeof(saddr);
do{
ret = connect(sockfd,(struct sockaddr *)&saddr,slen);
}while(ret != 0);
printf("connect successfully\n");
sleep(2);
while(i--)
{
write(sockfd,"hello",8);
read(sockfd,rdBuff,128);
printf("data = %s,pid = %lu\n",rdBuff,(unsigned long)getpid());
usleep(500000);
}
close(sockfd);
return 0;
}
1.c
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/msg.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<sys/stat.h>
int main()
{
int client_fd;
int i = 10;
int ret;
int sockfd,file_fd;
struct sockaddr_in saddr = {0};
socklen_t slen;
char rdBuff[128] = {0};
sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sockfd < 0)
{
perror("socket failed\n");
exit(1);
}
printf("sockfd:%d\n",sockfd);
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8090);
saddr.sin_addr.s_addr = htons(INADDR_ANY);
slen = sizeof(saddr);
clen = sizeof(caddr);
bind(sockfd,(struct sockaddr *)&saddr,slen);
signal_setup();
listen(sockfd,100);
while(1)
{
printf("listen ...\n");
client_fd = accept(sockfd,(struct sockaddr *)&caddr,&clen);
printf("connect successfully\n");
printf("client_fd:%d\n",client_fd);
printf("cip:%s\n",inet_ntoa(caddr.sin_addr));
pid = fork();
if(pid < 0)
{
perror("fork failed\n");
exit(1);
}
else if(pid == 0)
(3)结果:
先运行server.c,再运行1.c,紧接着运行client.c
(4)注意:
SIGCHLD信号产生的条件
1.子进程终止时会向父进程发送SIGCHLD信号,告知父进程回收自己,但该信号的默认处理动作为忽略,因此父进程仍然不会去回收子进程,需要捕捉处理实现子进程的回收;
2.子进程接收到SIGSTOP(19)信号停止时;
3.子进程处在停止态,接受到SIGCONT后唤醒时。
综上:子进程结束、接收到SIGSTOP停止(挂起)和接收到SIGCONT唤醒时都会向父进程发送SIGCHLD信号。父进程可以捕捉该信号,来实现对子进程的回收,或者了解子进程所处的状态。
(2)借助SIGCHLD信号回收子进程
子进程结束运行,其父进程会收到SIGCHLD信号。该信号的默认处理动作是忽略。
(5)参考:
(57条消息) SIGCHLD信号_luciusvorenus的博客-CSDN博客
(57条消息) linux中sigaction函数详解_魏波.的博客-CSDN博客
(57条消息) wait()和waitpid()解析_waitpid(null)是等待一个子进程完成吗_Randy__Lambert的博客-CSDN博客