process.h
#ifndef __PROCESSPOOL_H_ #define __PROCESSPOOL_H_ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <stdlib.h> #include <sys/epoll.h> #include <signal.h> #include <sys/wait.h> #include <sys/stat.h> #include <stdbool.h> /***描述一个子进程的类,m_pipefd是父进程和子进程通信用的管道***/ class process{ public: process():m_pid(-1){} public: pid_t m_pid; int m_pipefd[2]; }; /******进程池,将他定义为模版是为了代码复用,其模版参数是处理逻辑的类*******/ template<typename T> class processpool{ private: /*将构造函数定义为私有的,因为我们只能通过create静态函数来创建processpool实例*/ processpool(int listenfd,int process_number=8); public: /**单体模式,以保证程序最多创建一个processpool实例,这是程序正确的处理信号的必要条件**/ static processpool<T> *create(int listenfd,int process_number=8) { if(!m_instance) { m_instance=new processpool<T>(listenfd,process_number); } return m_instance; } ~processpool() { delete[] m_sub_process; } /**启动进程池**/ void run(); private: void setup_sig_pipe(); void run_parent(); void run_child(); private: /**进程池允许最大子进程数**/ static const int MAX_PROCESS_NUMBER=16; /** 每个子进程最多能处理的客户数量**/ static const int USER_PER_PROCESS=65536; /**epoll最多能处理事件的个数*/ static const int MAX_EVENT_NUMBER=10000; /**进程池中的进程数**/ int m_process_number; /**子进程在池中的序号,从0开始**/ int m_idx; /**每一个进程都有一个epoll内核事件表,用m_epollfd标识**/ int m_epollfd; /**监听socket是否停止*/ int m_listenfd; /**子进程通过m_stop来决定**/ int m_stop; /** 保存所有子进程的描述信息 **/ process *m_sub_process; /**进程池静态实例**/ static processpool<T> *m_instance; }; template <typename T> processpool<T> *processpool<T>::m_instance=NULL; /**用于信号处理的管道,以实现统一的事件源,后面称之为信号管道**/ static int sig_pipefd[2]; int setnonblocking(int fd) { int old_option=fcntl(fd,F_GETFL); int new_option=old_option | O_NONBLOCK; fcntl(fd,F_SETFL,new_option); return old_option; } static void addfd(int epollfd,int fd) { epoll_event event; event.data.fd=fd; event.events=EPOLLIN|EPOLLET; epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event); setnonblocking(fd); } /**从epollfd标识的epoll内核事件表中去除**/ static void removefd(int epollfd,int fd) { epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,0); close(fd); } /********************************* **********************************/ static void sig_handler(int sig) { int save_errno=errno; int msg=sig; send(sig_pipefd[1],(char *)&msg,1,0); //发送到写端 errno=save_errno; } /*********************************** ************************************/ static void addsig(int sig,void (handler)(int ),bool restart=true) { struct sigaction sa; memset(&sa,'\0',sizeof(sa)); sa.sa_handler=handler; if(restart) { sa.sa_flags |= SA_RESTART; } sigfillset(&sa.sa_mask); assert(sigaction(sig,&sa,NULL)!=-1); } /**进程池构造函数。参数listenfd是监听socket, 他必须在创建进程池之前被创建。否则子进程无法直接引用它**/ template<typename T> processpool<T>::processpool(int listenfd,int process_number):m_listenfd(listenfd) ,m_process_number() ,m_idx(-1) ,m_stop(false) { assert((process_number>0)&&(process_number<=MAX_PROCESS_NUMBER)); m_sub_process=new process[process_number]; assert(m_sub_process); /*****创建process_number个子进程,并建立它们和父进程之间的管道*****/ for(int i=0;i<process_number;i++) { int ret=socketpair(PF_UNIX,SOCK_STREAM,0,m_sub_process[i].m_pipefd); assert(ret==0); m_sub_process[i].m_pid=fork(); assert(m_sub_process[i].m_pid >= 0); if(m_sub_process[i].m_pid > 0) //父进程 { close(m_sub_process[i].m_pipefd[1]);//父进程关闭写端 continue; } else //子进程 { close(m_sub_process[i].m_pipefd[0]);//子进程关闭读端 m_idx=i; break; } } } /**统一事件源**/ template<typename T> void processpool<T>::setup_sig_pipe() { /** 创建epoll *事件和监听表和信号管道*/ m_epollfd=epoll_create(5); assert(m_epollfd!=-1); setnonblocking(sig_pipefd[1]); addfd(m_epollfd,sig_pipefd[0]); //将信号源的读端口文件描述符 添加到epoll /**设置信号处理函数**/ addsig(SIGCHLD,sig_handler); addsig(SIGTERM,sig_handler); addsig(SIGINT,sig_handler); addsig(SIGPIPE,SIG_IGN); } /** 父进程中m_idx值为-1,子进程中m_idx值大于等于0, 我们据此半段接下来要运行的父进程代码还是子进程代码 **/ template<typename T> void processpool<T>::run() { if(m_idx != -1) { run_child(); return ; } run_parent(); } template<typename T> void processpool<T>::run_child() { setup_sig_pipe(); /*每个子进程都通过其在进程池中序号值m_idx找到与父进程通信的管道*/ int pipefd=m_sub_process[m_idx].m_pipefd[1]; addfd(m_epollfd,pipefd); epoll_event events[MAX_EVENT_NUMBER]; T *user=new T[USER_PER_PROCESS]; assert(user); int number=0; int ret=-1; while(!m_stop) { number=epoll_wait(m_epollfd,events,MAX_EVENT_NUMBER,-1); if((number<0) && (errno != EINTR)) { printf("epoll failuer\n"); break; } for(int i=0;i<number;i++) { int sockfd=events[i].data.fd; if((sockfd==pipefd)&&(events[i].events & EPOLLIN)) { int client=0; ret=recv(sockfd,(char *)&client,sizeof(client),0); if(( (ret<0) && (errno!=EAGAIN)) || ret ==0 ) { continue; } else { struct sockaddr_in client_address; socklen_t client_addrlength=sizeof(client_address); int connfd=accept(m_listenfd,(struct sockaddr *) &client_address,&client_addrlength); if(connfd < 0) { printf("errno is:%d\n",errno); continue; } addfd(m_epollfd,connfd); /**模版类T必须实现init方法,以初始化一个客户端连接。 我们直接使用connfd来索引逻辑处理对象,提高程序效率**/ user[connfd].init(m_epollfd,connfd,client_address); } } else if((sockfd == sig_pipefd[0]) && (events[i].events&EPOLLIN)) { int sig; char signals[1024]; ret=recv(sig_pipefd[0],signals,sizeof(signals),0); if(ret <= 0) { continue; } else { for(int i=0;i<ret;++i) { switch(signals[i]) { case SIGCHLD: { pid_t pid; int stat; while((pid = waitpid(-1,&stat,WNOHANG))>0) { continue; } break; } case SIGTERM: case SIGINT: { m_stop = true; break; } default: break; } } } } /**如果是其他可读数据,那么必然是客户请求到来。调用逻辑处理对象的process方法处理之**/ else if(events[i].events & EPOLLIN) { user[sockfd].process(); } else { continue; } } } delete[] user; user = NULL; close(pipefd); //close(m_listenfd); /*我们将这句话注释掉,以提醒读者;应该由m_listenfd 的创建者来关闭这个文件描述符,即所谓的“对象” (比如一个文件描述符,又或者一段堆内存) 由哪个函数创建,就应该由哪个函数销毁 */ close(m_epollfd); } template<typename T> void processpool<T>::run_parent() { setup_sig_pipe(); /** 父进程监听 m_listenfd **/ addfd(m_epollfd,m_listenfd); epoll_event events[MAX_EVENT_NUMBER]; int sub_process_counter=0; int new_conn=1; int number=0; int ret=-1; while(!m_stop) { number=epoll_wait(m_epollfd,events,MAX_EVENT_NUMBER,-1); if((number<0) && (errno != EINTR)) { printf("epoll failuer\n"); break; } for(int i=0;i<number;i++) { int sockfd=events[i].data.fd; if(sockfd == m_listenfd) { /** 如果有新连接到来,就采用Round Robin方式将其分配给一个子进程处理 **/ int i=sub_process_counter; do { if(m_sub_process[i].m_pid != -1) break; i=(i+1)%m_process_number; }while(i != sub_process_counter); if(m_sub_process[i].m_pid == -1) { m_stop = true; break; } sub_process_counter=(i+1)%m_process_number; send(m_sub_process[i].m_pipefd[0],(char *)&new_conn,sizeof(new_conn),0); printf("send request to child %d\n",i); } /*** 处理父进程接收到的信号 ***/ else if((sockfd == sig_pipefd[0]) && (events[i].events & EPOLLIN)) { int sig; char signals[1024]; ret=recv(sig_pipefd[0],signals,sizeof(signals),0); if(ret <= 0) { continue; } else { for(int i=0;i<ret;i++) { switch(signals[i]) { case SIGCHLD: { pid_t pid; int stat; while((pid=waitpid(-1,&stat,WNOHANG))>0) { for(int i=0;i<m_process_number;i++) { /** 如果进程池中第i个子进程退出了,则主进程关闭相应的通信管道。 并设置相应的m_Pid为-1,以标记该子进程已经退出 **/ if(m_sub_process[i].m_pid == pid) { printf("child %d join\n",i); close(m_sub_process[i].m_pipefd[0]); m_sub_process[i].m_pid=-1; } } } /** 如果所有子进程都已经退出,则父进程也退出 **/ m_stop=true; for(int i=0;i<m_process_number;++i) { if(m_sub_process[i].m_pid != -1) { m_stop=false; } } break; } case SIGTERM: case SIGINT: { /*如果父进程接收到终止信号,那么就杀死所有子进程,并等待他们全部结束。 当然,通知子进程结束更好的方法是向父, 子进程之间的通信管道发送特殊数据,读者不妨自己实现之*/ printf("kill all the child now\n"); for(int i=0;i<m_process_number;i++) { int pid=m_sub_process[i].m_pid; if(pid != -1) { kill(pid,SIGTERM); } } break; } default: break; } } } } else { continue; } } } //close(m_listenfd); 由创建者关闭这个进程 close(m_epollfd); } #endif
cgin_conn.cpp:
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <stdlib.h> #include <sys/epoll.h> #include <signal.h> #include <sys/wait.h> #include <sys/stat.h> #include "processpool.h" /* 用于处理客户CGI 请求的类,它可以作为processpool 类的模版参数 */ class cgi_conn { public: cgi_conn(){} ~cgi_conn(){} /* 初始化客户连接,清空读缓冲区 */ void init(int epollfd,int sockfd,const sockaddr_in& client_addr) { m_epollfd=epollfd; m_sockfd=sockfd; m_address = client_addr; memset(m_buf,'\0',BUFFER_SIZE); m_read_idx=0; } void process() { int idx=0; int ret=-1; /* 循环读取和分析客户数据 */ while(true) { idx=m_read_idx; ret=recv(m_sockfd,m_buf+idx,BUFFER_SIZE-1-idx,0); /* 如果读操作发生错误,则关闭客户连接,但如果是暂时无数据可读,则退出循环 */ if(ret < 0) { if(errno != EAGAIN) { removefd(m_epollfd,m_sockfd); } break; } /* 如果对方关闭连接,则服务器也关闭连接 */ else if(ret == 0) { removefd(m_epollfd,m_sockfd); break; } else { m_read_idx += ret; printf("user content is :%s\n",m_buf); /* 如果遇到字符"\r\n",则开始处理客户请求*/ for(;idx < m_read_idx;++idx) { if((idx >= 1) && (m_buf[idx-1]=='\r')&&(m_buf[idx]=='\n')) { break; } } /* 如果没有遇到字符'\r\n',则需要读取更多客户数据*/ if(idx == m_read_idx) continue; m_buf[idx-1]='\0'; char *file_name=m_buf; /* 判断客户要运行的VGI程序是否存在 */ if(access(file_name,F_OK) == -1) { removefd(m_epollfd,m_sockfd); break; } /* 创建子进程来执行CGI 程序 */ ret = fork(); if(ret == -1) { removefd(m_epollfd,m_sockfd); break; } else if(ret > 0) { /* 父进程只需关闭连接 */ removefd(m_epollfd,m_sockfd); break; } else { /* 子进程将标准输出定向到m_sockfd,并执行CGI 程序 */ close(STDOUT_FILENO); dup(m_sockfd); execl(m_buf,m_buf,0); exit(0); } } } } private: /* 读缓冲区的大小 */ static const int BUFFER_SIZE = 1024; static int m_epollfd; int m_sockfd; sockaddr_in m_address; char m_buf[BUFFER_SIZE]; /* 标记读缓冲中已经读入的客户数据的最后一个字节的下一个位置 */ int m_read_idx; }; int cgi_conn::m_epollfd=-1; int main(int argc,char *argv[]) { if(argc <= 2) { printf("usage:%s ip_address port_number\n", basename(argv[0])); return 1; } const char *ip=argv[1]; int port =atoi(argv[2]); int listenfd=socket(AF_INET,SOCK_STREAM,0); assert(listenfd >= 0); int ret=0; struct sockaddr_in address; bzero(&address,sizeof(address)); address.sin_family=AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port=htons(port); ret=bind(listenfd,(struct sockaddr *)&address,sizeof(address)); assert(ret != -1); ret = listen(listenfd,5); assert(ret != -1); processpool<cgi_conn> *pool = processpool<cgi_conn>::create(listenfd); if(pool) { pool->run(); delete pool; } /*正如前文提到的,main函数创建了文件描述符 listenfd,那么就由它亲自关闭之*/ close(listenfd); return 0; }