1.直接贴上源码:
注:此处使用了对常见的网络编程函数异常处理后的头文件wrap.c(下载于网络)
// 目的:使用多进程处理客户端发来的请求
// 步骤:
// 1.socket()创建监听文件描述符 lfd
// 2.bind() 将lfd与ip和port绑定
// 3.listen() 设置为被动监听模式
// 4.while(1)
// {
// cfd = accept()
// pid = fork()创建子进程
// 父进程,回收子进程
// 子进程,循环读写,循环退出时,注意:子进程不能再循环去创建子进程
// }
//
// <使用自定义的头文件实现>
//
#include "wrap.h"
#include <ctype.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
// 回收子进程的信号处理函数。
// 可以应对的场景有:
// 1.在绑定信号处理函数之前,子进程已全部退出
// 2.绑定信号处理函数之前,子进程退出了一部分,之后子进程相继推出
// 3.绑定之前都为退出,之后陆续退出(其实和第二点一样啦~~)
// pid_t waitpid(pid_t pid, int *wstatus, int options);
void cleanSons()
{
while(1)
{
// sleep(1);
pid_t ret = waitpid(-1,NULL,WNOHANG);
if(ret > 0)
{
printf("已回收掉进程[%d]\n",ret);
}
else if(ret == 0)
{
// printf("~_~\t");
printf("子进程在运行\n");
break; // 对应一个一个退出的情况!!!
}
else if(ret < 0)
{
printf("子进程已全部回收\n");
break;
}
}
}
int main()
{
// --1--生成监听文件描述符 int socket(int domain, int type, int protocol);
int lfd = Socket(AF_INET, SOCK_STREAM, 0);
// --2--绑定端口 int bind(int sockfd, const struct sockaddr *addr,
// socklen_t addrlen);
struct sockaddr_in serv;
bzero(&serv, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
// --3-- 设置为监听模式 int listen(int sockfd, int backlog);
Listen(lfd, 128);
// --4-- 进入while循环,监听连接请求,建立相应子进程
int cfd;
socklen_t len;
pid_t pid;
while(1)
{
// --接受连接,建立子进程
// --接受连接--int accept(int sockfd, struct sockaddr *addr,
// socklen_t *addrlen);
struct sockaddr_in client;
len = sizeof(client);
cfd = Accept(lfd, (struct sockaddr *)&client, &len);
// 打印客户端的信息
char IP[16];
inet_ntop(AF_INET, &client.sin_addr.s_addr, IP, sizeof(IP));
printf("client IP = [%s], port = [%d]", IP, ntohs(client.sin_port));
// 创建子进程之前,先阻塞SIGCHLD信号,避免子进程回收函数注册之前,子进程已全部退出。避免子进程成为僵尸进程
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask,SIGCHLD);
sigprocmask(SIG_BLOCK,&mask,NULL);
// --fork子进程 --
pid = fork();
if(pid < 0)
{
perror("fork error");
return -1;
}
else if(pid > 0)
{
//关闭连接文件描述符
close(cfd);
printf("hello\n");
//回收子进程
//注册子进程回收信号处理函数
// int sigaction(int signum, const struct sigaction *act,
// struct sigaction *oldact);
struct sigaction act;
act.sa_handler = cleanSons;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
act.sa_flags = 0;
sigaction(SIGCHLD,&act,NULL);
sigprocmask(SIG_UNBLOCK,&mask,NULL); //解除对SIGCHLD的屏蔽
}
else if(pid == 0)
{
//关闭通信文件描述符
close(lfd);
// 处理业务逻辑 读写
char buf[128];
int n = 0;
int i = 0;
while(1)
{
memset(buf, 0x00, sizeof(buf));
n = Read(cfd, buf, sizeof(buf));
printf("%s\n", buf);
if(n <= 0)
{
perror("read error or client closed");
break;
}
for(i = 0; i < n; i++)
{
buf[i] = toupper(buf[i]);
}
Write(cfd, buf, sizeof(buf));
}
// 读错误退出业务逻辑
close(cfd);
exit(-1); // 也可以 break,退出外面的 while(1)循环,接着就退出了整个子进程
// exit() 更直观一点,表示到此没有什么要处理的了,直接退出整个进程吧~
}
}
return 0;
}
2.头文件
可以直接搜索 wrap.c 得到 头文件的实现代码 wrap.c 和头文件的声明 wrap.h (emm~贴上来太长了,自行下载哦)
3.实验:
①:首先运行起来服务端:
②:接着创建三个客户端连接服务端:
③:接着查看一下相关进程信息:
可以看到父进程已经拉起了3个子进程。
④:
下面就要测试一下关掉一个客户端后,break退出子进程在 waile(1) 的业务逻辑,接着 exit(-1) 后子进程退出。(简单说就是 关闭一个客户端,使得一个子进程退出)
父进程是否能对子进程进行回收。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
好了,试一下吧~
再查看相关进程信息:
关闭了两个客户端,杀死了两个子进程,没有出现僵尸进程。
再看一下服务端的日志打印:
也打印出了回收的子进程,是相互印证的。
over ~~