我们上一节服务器他有些局限,同一时间只能接收一个服务器端连接,这样的设计无疑是很多缺陷的,我们先来写个多进程服务器思路如下: 主进程阻塞等待新的连接,创建出新的连接后顺带创建一个子进程去通信,代码如下:
/* File Name: server.c */ #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<errno.h> #include<pthread.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <ctype.h> #include<arpa/inet.h> #include<string.h> void sys_err(const char *str) { perror(str); exit(1); } #define SERV_PORT 8888 int main(void) { int lfd=0,cfd=0; pid_t pid; int ret; char buf[BUFSIZ]; struct sockaddr_in serv_addr; struct sockaddr_in client_addr; socklen_t client_addr_len; serv_addr.sin_family=AF_INET; serv_addr.sin_port=htons(SERV_PORT); serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); lfd=socket(AF_INET,SOCK_STREAM,0); if(lfd==-1){ sys_err("socket error"); } bind(lfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); listen(lfd,128); client_addr_len=sizeof(client_addr); while (1) { cfd=accept(lfd,(struct sockaddr*)&client_addr,&client_addr_len); //主进程阻塞等待新的连接 //if(cfd==-1) //{ // sys_err("socket error for coonect"); //} pid=fork(); if (pid==-1) { printf("进程创建失败"); } else if(pid==0)//pid=0证明执行的是子进程 { break; } else { close(cfd);//主进程不需要lfd } } close(lfd);//执行到这一步证明是子进程,子进程不需要lfd,所以close if(pid==0)//这里执行子进程,进行与服务器间的通信 { while (1) { ret=read(cfd,buf,sizeof(buf)); if(ret==0) { close(cfd); exit(1); } for(int i=0;i<ret;i++) { buf[i]=toupper(buf[i]); } write(cfd,buf,ret); write(STDOUT_FILENO,buf,ret); } } return 0; }这时候,我们运行起服务器的同时运行多个终端输入命令
nc 127.0.0.1 8888可以看到实现效果如下:,几个客户端都能够同时跟服务器建立连接并通信 这个时候我们停止三个客户端,同时输入ps ajx查看进程状态,可以看到有四个server在运行,子进程并没有完全退出,还留了个僵尸进程在这
因为我们并没有对子进程进行一个回收,所以导致了这些僵尸进程的产生,所以我们要在父进程时当子进程工作完成后调用wait或waitpid函数对他进行一个回收 那么子进程退出时,会不会告诉父进程它要退出了?其实在子进程退出时,会给父进程发送一个SIGCHLD,17号信号。所以我们直接捕捉这个信号处理为子进程收尸就可以 代码如下:
/* File Name: server.c */ #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<errno.h> #include<pthread.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <ctype.h> #include<arpa/inet.h> #include<string.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> void sys_err(const char *str) { perror(str); exit(1); } void catch_sig(int num) { printf("catch %d sig\n",num); while(waitpid(-1,NULL,0)); //定义捕捉完信号后的处理函数,sleep5秒模拟处理函数所需要的时间 } #define SERV_PORT 8888 int main(void) { struct sigaction act; act.sa_flags=0; sigemptyset(&act.sa_mask); act.sa_handler=catch_sig; //上面三初始化sigaction结构体,可以看博主前期的注册信号的文章 sigaction(SIGCHLD,&act,NULL);//注册捕捉函数 //声明要捕捉的信号 int lfd=0,cfd=0; pid_t pid; int ret; char buf[BUFSIZ]; struct sockaddr_in serv_addr; struct sockaddr_in client_addr; socklen_t client_addr_len; serv_addr.sin_family=AF_INET; serv_addr.sin_port=htons(SERV_PORT); serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); lfd=socket(AF_INET,SOCK_STREAM,0); if(lfd==-1){ sys_err("socket error"); } bind(lfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); listen(lfd,128); client_addr_len=sizeof(client_addr); while (1) { cfd=accept(lfd,(struct sockaddr*)&client_addr,&client_addr_len); //主进程阻塞等待新的连接 // if(cfd==-1) // { // sys_err("socket error for coonect"); // } pid=fork(); if (pid==-1) { printf("进程创建失败"); } else if(pid==0)//pid=0证明执行的是子进程 { break; } else { close(cfd);//主进程不需要cfd //主进程开始准备注册信号捕捉函数 } } close(lfd);//执行到这一步证明是子进程,子进程不需要lfd,所以close if(pid==0)//这里执行子进程,进行与服务器间的通信 { while (1) { ret=read(cfd,buf,sizeof(buf)); if(ret==0) { close(cfd); exit(1); } for(int i=0;i<ret;i++) { buf[i]=toupper(buf[i]); } write(cfd,buf,ret); write(STDOUT_FILENO,buf,ret); } } return 0; }再新开3个终端输入nc 127.0.0.1 8888进行连接,如下图可以看到有四个server进程 我们再退出,然后再输入ps ajx查看 只剩一个父进程了,至此为止,我们的多进程服务器搭建完毕。