epoll模型主要有2种工作方式:水平触发(LT)和边缘触发(ET),本文主要是关于边缘触发的。本文实现的epoll多线程模型主要是,主线程等待事件触发,然后把相关事件放入队列,线程池从队列中取出数据处理事件。
下面是具体实现:
#include <stdarg.h>#include <errno.h>#include <stdio.h>#include <fcntl.h>#include <unistd.h>#include <time.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/epoll.h>#include <sys/sendfile.h>#include <dirent.h>#include <netinet/in.h>#include <sys/socket.h>#include <resolv.h>#include <arpa/inet.h>#include <stdlib.h>#include <signal.h>#include <getopt.h>#include <string.h>#include <string>#include <iostream>#include "ThreadPool.h"using namespace std;static string strIP="192.168.0.168";static int nPort=8088;static string strDir="/home/temp/http_server";const int MAX_EVENT=10;struct epoll_event ev, events[MAX_EVENT];int epfd;enum {NeedRead_Event,Reading_Event,Error_Req,NeedWrite_Event,Writing_Event};struct Task{epoll_event m_oEvent;string m_strFilePath;int m_nCurSize;int m_nType;int m_nReadOrWritefd;int m_nFileLen;int m_nSockfd;};char* dir_up(char *Path){int len;len = strlen(Path);if (len > 1 && Path[len - 1] == '/'){len--;}while (Path[len - 1] != '/' && len > 1){len--;}Path[len] = 0;return Path;}char *strsplit(char **s,char del){char *d, *tok;if (!s || !*s)return NULL;tok = *s;d = strchr(tok, del);if (d) {*d = '\0';*s = d + 1;} else*s = NULL;return tok;}char* urldecode( char* encd, char* decd){int j,i;char *cd = encd;char p[2];unsigned int num;j=0;for( i = 0; i < strlen(cd); i++ ){memset( p, '\0', 2 );if( cd[i] != '%' ){decd[j++] = cd[i];continue;}p[0] = cd[++i];p[1] = cd[++i];p[0] = p[0] - 48 - ((p[0] >= 'A') ? 7 : 0) - ((p[0] >= 'a') ? 32 : 0);p[1] = p[1] - 48 - ((p[1] >= 'A') ? 7 : 0) - ((p[1] >= 'a') ? 32 : 0);decd[j++] = (p[0] * 16 + p[1]);}decd[j] = '\0';return decd;}void *ReadTask(void *arg){Task *pTask=(Task*)arg;char *cBuffer=new char[128*1024];if(pTask->m_nType==NeedRead_Event){int nSize=read(pTask->m_nSockfd,cBuffer,128*1024);if(nSize==0){close(pTask->m_nSockfd);epoll_ctl(epfd, EPOLL_CTL_DEL,pTask->m_nSockfd, &(pTask->m_oEvent));delete pTask;return NULL;}else if(nSize<0){cout<<errno<<endl;if (errno == EINTR){pTask->m_oEvent.events = EPOLLIN | EPOLLET | EPOLLONESHOT;epoll_ctl(epfd, EPOLL_CTL_MOD,pTask->m_nSockfd, &(pTask->m_oEvent));return NULL;}if (errno == EAGAIN){return NULL;}}else{char *cMethon=strsplit(&cBuffer,' ');if(cBuffer==NULL){pTask->m_nType=Error_Req;pTask->m_oEvent.events = EPOLLOUT | EPOLLET | EPOLLONESHOT;epoll_ctl(epfd, EPOLL_CTL_MOD,pTask->m_nSockfd, &(pTask->m_oEvent));return NULL;}char *cUrl=strsplit(&cBuffer,' ');if(cUrl==NULL){pTask->m_nType=Error_Req;pTask->m_oEvent.events = EPOLLOUT | EPOLLET | EPOLLONESHOT;epoll_ctl(epfd, EPOLL_CTL_MOD,pTask->m_nSockfd, &(pTask->m_oEvent));return NULL;}char *decUrl=new char [strlen(cUrl)];urldecode(cUrl,decUrl);pTask->m_nType=NeedWrite_Event;//pTask->m_strFilePath=UrlDecode(cUrl);pTask->m_strFilePath=decUrl;delete [] decUrl;pTask->m_oEvent.events = EPOLLOUT | EPOLLET | EPOLLONESHOT;epoll_ctl(epfd, EPOLL_CTL_MOD, pTask->m_nSockfd, &(pTask->m_oEvent));return NULL;}}else{//处理post消息类型或者大文件return NULL;}}const string strError="HTTP/1.1 404 ERROR\r\nServer:SimpleServer\r\nConnection: close\r\nContent-Type:text/html; charset=UTF-8\r\n";void *WriteTask(void *arg){Task *pTask=(Task*)arg;char cBuffer[128*1024];if(pTask->m_nType==NeedWrite_Event){struct dirent *pDir;struct stat oStat;DIR *dir;string strFilePath=strDir+pTask->m_strFilePath;if(stat(strFilePath.c_str(),&oStat)){//需要对返回值进行判断,这边不会溢出所以简单处理,实际上需要处理int nSize=sprintf(cBuffer,"HTTP/1.1 200 OK\r\nServer:SimpleServer\r\nConnection: close\r\nContent-Type:text/html; charset=UTF-8\r\n\r\n<html><head><title>%d - %s</title></head>" "<body><font size=+4>Linux directory access server</font><br><hr width=\"100%%\"><br><center>" "<table border cols=3 width=\"100%%\">", errno, strerror(errno));nSize+=sprintf(cBuffer+nSize,"</table><font color=\"CC0000\" size=+2>Please contact the administrator consulting why appear as follows error message:\n%s %s</font></body></html>", pTask->m_strFilePath.c_str(), strerror(errno));int nWriteCount=write(pTask->m_nSockfd,cBuffer,nSize);/*if(nWriteCount<0){if(errno == EAGAIN){pTask->m_nType=Writing_Event;pTask->m_oEvent.events = EPOLLOUT | EPOLLET | EPOLLONESHOT;epoll_ctl(epfd, EPOLL_CTL_MOD,pTask->m_nSockfd, &(pTask->m_oEvent));}}*/close(pTask->m_nSockfd);epoll_ctl(epfd, EPOLL_CTL_DEL,pTask->m_nSockfd, &(pTask->m_oEvent));delete pTask;return NULL;}if(S_ISREG(oStat.st_mode)){pTask->m_nReadOrWritefd = open(strFilePath.c_str(), O_RDONLY);pTask->m_nFileLen = lseek(pTask->m_nReadOrWritefd, 0, SEEK_END);lseek(pTask->m_nReadOrWritefd, 0, SEEK_SET);int nSize=sprintf(cBuffer,"HTTP/1.1 200 OK\r\nServer:SimpleServer\r\nConnection: keep-alive\r\nContent-type: application/*\r\nContent-Length:%d\r\n\r\n",pTask->m_nFileLen);write(pTask->m_nSockfd,cBuffer,nSize);pTask->m_nType=Writing_Event;pTask->m_nCurSize=0;pTask->m_oEvent.events = EPOLLOUT | EPOLLET | EPOLLONESHOT;epoll_ctl(epfd, EPOLL_CTL_MOD,pTask->m_nSockfd, &(pTask->m_oEvent));}else if(S_ISDIR(oStat.st_mode)){int nSize=0;dir=opendir(strFilePath.c_str());nSize+=sprintf(cBuffer+nSize, "HTTP/1.1 200 OK\r\nServer:SimpleServer\r\nConnection: close\r\nContent-Type:text/html; charset=UTF-8\r\n\r\n<html><head><title>%s</title></head>""<body><font size=+4>Linux directory access server</font><br><hr width=\"100%%\"><br><center>""<table border cols=3 width=\"100%%\">", strFilePath.c_str());nSize+=sprintf(cBuffer+nSize, "<caption><font size=+3>dir %s</font></caption>\n",strFilePath.c_str());nSize+=sprintf(cBuffer+nSize, "<tr><td>name</td><td>大小</td><td>change time</td></tr>\n");if(dir==NULL){nSize+=sprintf(cBuffer+nSize,"</table><font color=\"CC0000\" size=+2>%s</font></body></html>",strerror(errno));write(pTask->m_nSockfd,cBuffer,nSize);return NULL;}while((pDir=readdir(dir))!=NULL){string strFileName=string(pTask->m_strFilePath.c_str())+"/"+string(pDir->d_name);nSize+=sprintf(cBuffer+nSize,"<tr>");string strDirFilePath=strFilePath+"/"+pDir->d_name;if(stat(strDirFilePath.c_str(),&oStat)==0){if(strcmp(pDir->d_name, "..") == 0){char path[PATH_MAX];strcpy(path,pTask->m_strFilePath.c_str());nSize+=sprintf(cBuffer+nSize,"<td><a href=\"http://%s:%d%s\">..</a></td>",strIP.c_str(), nPort,dir_up(path));}else if(strcmp(pDir->d_name,".")==0){nSize+=sprintf(cBuffer+nSize,"<td><a href=\"http://%s:%d%s\">.</a></td>",strIP.c_str(),nPort, pTask->m_strFilePath.c_str());}else{nSize+=sprintf(cBuffer+nSize,"<td><a href=\"http://%s:%d%s\">%s</a></td>", strIP.c_str(),nPort, strFileName.c_str(),pDir->d_name);}if (S_ISDIR(oStat.st_mode)){nSize+=sprintf(cBuffer+nSize, "<td>dir</td>");}else if (S_ISREG(oStat.st_mode)){nSize+=sprintf(cBuffer+nSize, "<td>%d</td>", oStat.st_size);}else if (S_ISLNK(oStat.st_mode)){nSize+=sprintf(cBuffer+nSize, "<td>interlinkage</td>");}else if (S_ISCHR(oStat.st_mode)){nSize+=sprintf(cBuffer+nSize, "<td>char device</td>");}else if (S_ISBLK(oStat.st_mode)){nSize+=sprintf(cBuffer+nSize, "<td>chunk device</td>");}else if (S_ISFIFO(oStat.st_mode)){nSize+=sprintf(cBuffer+nSize, "<td>FIFO</td>");}else if (S_ISSOCK(oStat.st_mode)){nSize+=sprintf(cBuffer+nSize, "<td>Socket</td>");}else{nSize+=sprintf(cBuffer+nSize, "<td>(unknow)</td>");nSize+=sprintf(cBuffer+nSize, "<td>%s</td>", ctime(&oStat.st_ctime));}}nSize+=sprintf(cBuffer+nSize, "</tr>\n");}closedir(dir);nSize+=sprintf(cBuffer+nSize, "</tr>\n");write(pTask->m_nSockfd,cBuffer,nSize);close(pTask->m_nSockfd);epoll_ctl(epfd, EPOLL_CTL_DEL,pTask->m_nSockfd, &(pTask->m_oEvent));delete pTask;}else{int nSize=0;nSize+=sprintf(cBuffer+nSize,"HTTP/1.1 200 OK\r\nServer:SimpleServer\r\nConnection: close\r\nContent-Type:text/html; charset=UTF-8\r\n<html><head><title>permission denied</title></head>" "<body><font size=+4>Linux directory access server</font><br><hr width=\"100%%\"><br><center>" "<table border cols=3 width=\"100%%\">");nSize+=sprintf(cBuffer+nSize,"</table><font color=\"CC0000\" size=+2>You visit resources '%s' be under an embargo,Please contact the administrator to solve!</font></body></html& gt;", pTask->m_strFilePath.c_str());write(pTask->m_nSockfd,cBuffer,nSize);close(pTask->m_nSockfd);epoll_ctl(epfd, EPOLL_CTL_DEL,pTask->m_nSockfd, &(pTask->m_oEvent));delete pTask;}}else if(pTask->m_nType==Writing_Event){int nSize=128*1024;if(pTask->m_nFileLen-pTask->m_nCurSize<128*1024){nSize=pTask->m_nFileLen-pTask->m_nCurSize;}nSize=sendfile(pTask->m_nSockfd,pTask->m_nReadOrWritefd,NULL,nSize);pTask->m_nCurSize+=nSize;if(pTask->m_nCurSize!=pTask->m_nFileLen){pTask->m_oEvent.events = EPOLLOUT | EPOLLET | EPOLLONESHOT;epoll_ctl(epfd, EPOLL_CTL_MOD,pTask->m_nSockfd, &(pTask->m_oEvent));}else{close(pTask->m_nReadOrWritefd);close(pTask->m_nSockfd);epoll_ctl(epfd, EPOLL_CTL_DEL,pTask->m_nSockfd, &(pTask->m_oEvent));delete pTask;}}else if(pTask->m_nType==Error_Req){write(pTask->m_nSockfd,strError.c_str(),strError.size());close(pTask->m_nSockfd);epoll_ctl(epfd, EPOLL_CTL_DEL,pTask->m_nSockfd, &(pTask->m_oEvent));delete pTask;}return NULL;}void setnonblocking(int sock){int opts;opts = fcntl(sock, F_GETFL);if (opts < 0){cout<<"fcntl(sock,F_GETFL) error"<<endl;return ;}opts = opts | O_NONBLOCK;if (fcntl(sock, F_SETFL, opts) < 0){cout<<"fcntl(sock,F_GETFL) error"<<endl;return ;}}int main(){ThreadPool mPool;//signal(SIGPIPE,SIG_IGN);//signal(SIGCHLD,SIG_IGN);epfd = epoll_create(MAX_EVENT);struct sockaddr_in addr;int sock_fd,addrlen;if((sock_fd=socket(PF_INET,SOCK_STREAM,0))<0){cout<<"socket init failed..."<<endl;}addrlen = 1;setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &addrlen, sizeof(addrlen));setnonblocking(sock_fd);addr.sin_family=AF_INET;addr.sin_port=htons(nPort);addr.sin_addr.s_addr=htonl(INADDR_ANY);addrlen=sizeof(struct sockaddr_in);if(bind(sock_fd,(struct sockaddr*)&addr,addrlen)<0){cout<<"bind failed..."<<endl;}if(listen(sock_fd,MAX_EVENT)<0){cout<<"listen failed..."<<endl;}cout<<"server start..."<<endl;ev.data.fd = sock_fd;// 设置要处理的事件类型ev.events = EPOLLIN | EPOLLET;// 注册 epoll 事件epoll_ctl(epfd, EPOLL_CTL_ADD, sock_fd, &ev);struct sockaddr_in clientaddr ;for (;;){int nfds = epoll_wait(epfd, events, MAX_EVENT, -1);for(int i=0;i<nfds;++i){if(events[i].data.fd==sock_fd){int connfd;socklen_t clilen = sizeof(clientaddr);connfd = accept(sock_fd, (sockaddr *)&clientaddr, &clilen);if(connfd == -1){cout<<"accept error:"<<errno<<endl;continue;}else{cout<<"connect from:"<<inet_ntoa(clientaddr.sin_addr)<<":"<<ntohs(clientaddr.sin_port)<<endl;}setnonblocking(connfd);int nRecvBuf=128*1024;setsockopt(connfd, SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));int nSendBuf=128*1024;setsockopt(connfd, SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));int nNetTimeout=3000;setsockopt(connfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&nNetTimeout, sizeof(int));setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&nNetTimeout, sizeof(int));Task *pTask=new Task;pTask->m_nType=NeedRead_Event;pTask->m_nSockfd=connfd;pTask->m_oEvent.events = EPOLLIN | EPOLLET |EPOLLONESHOT;pTask->m_oEvent.data.ptr = pTask;epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &(pTask->m_oEvent));}else if(events[i].events & EPOLLIN){mPool.AddWorker(ReadTask,events[i].data.ptr);}else if(events[i].events & EPOLLOUT){mPool.AddWorker(WriteTask,events[i].data.ptr);}}}}