Linux網路程式設計---I/O多路複用之epoll
阿新 • • 發佈:2019-02-10
/* TCP伺服器 用法:./server port */ #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <time.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/wait.h> #include <assert.h> #include <vector> #include <fcntl.h> #include <sys/epoll.h> #include <algorithm> using namespace std; #define BUFSIZE 1024 #define MAXCONN 200 static void bail(const char *on_what) { fputs(strerror(errno),stderr); fputs(": ",stderr); fputs(on_what, stderr); fputc('\n',stderr); exit(1); } struct sockfd_opt //處理每個socket描述符的結構體 { int fd; //描述符 int (*do_task)(struct sockfd_opt *p_so); //回撥函式 }; vector<struct sockfd_opt*>HashHead[MAXCONN]; //連結串列元素 int epfd; struct epoll_event *events; //設定為非阻塞模式 void setnonblocking(int sock) { int opts; opts=fcntl(sock, F_GETFL); if (opts<0) bail("fcntl"); opts=opts|O_NONBLOCK; if (fcntl(sock, F_SETFL,opts)<0) bail("fcntl"); } //生成hash值 int intHash(int key) { key+=~(key<<15); key^=(key>>10); key+=(key<<3); key^=(key>>6); key+=~(key<<11); key^=(key>>16); return key; } //向客戶端發回日期時間 int send_reply(struct sockfd_opt *p_so) { char reqBuf[BUFSIZE]; //接收快取 char dtfmt[BUFSIZE];//日期-時間結果字串 time_t td; //當前時間和日期 struct tm tm; long z; unsigned int hash; if ((z=read(p_so->fd, reqBuf, sizeof(reqBuf)))<=0) { //此fd代表的客戶端關閉了連線,因此該fd將自動從epfd中刪除,於是我們僅需將其從散列表中刪除 hash=intHash(p_so->fd)& MAXCONN; vector<struct sockfd_opt*>::iterator it; HashHead[hash].erase(find(HashHead[hash].begin(), HashHead[hash].end(), p_so)); //刪除 //關閉當前套接字描述符 close(p_so->fd); free(p_so); //若讀操作返回-1且不是RST分段 if (z<0 && (errno|=ECONNRESET)) bail("read()"); } else { reqBuf[z]=0; time(&td); tm=*localtime(&td); strftime(dtfmt, sizeof(dtfmt), reqBuf, &tm); //向客戶端發回結果 z=write(p_so->fd, dtfmt, strlen(dtfmt)); if (z<0) bail("write()"); } return 0; } //接收TCP連線 int creat_conn(struct sockfd_opt *p_so) { unsigned int hash; struct sockaddr_in client; //客戶端ip地址 int conn_fd; socklen_t sin_size; sin_size=sizeof(client); struct epoll_event ev; if ((conn_fd=accept(p_so->fd, (struct sockaddr*)&client, &sin_size))==-1) { fprintf(stderr, "Accept error:%s\a\n",strerror(errno)); exit(1); } setnonblocking(conn_fd); fprintf(stdout, "server got connection from %s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); int ret; if ((p_so=(struct sockfd_opt*)malloc(sizeof(sockfd_opt)))==NULL) { perror("malloc"); return -1; } p_so->fd=conn_fd; p_so->do_task=send_reply; hash=intHash(conn_fd)&MAXCONN; HashHead[hash].push_back(p_so); // printf("fd2:%d hash2:%d size2:%d\n",conn_fd,hash,HashHead[hash].size()); //向epoll上下文註冊此conn_fd ev.data.fd=conn_fd; ev.events=EPOLLIN; //ev.events=EPOLLIN|EPOLLET //新增此fd ret=epoll_ctl(epfd,EPOLL_CTL_ADD,conn_fd,&ev); if (ret) bail("epoll_ctl"); return 0; } //初始化監聽套接字選項 int init(int fd) { sockfd_opt *p_so; struct epoll_event ev; unsigned int hash; int ret; if ((p_so=(struct sockfd_opt*)malloc(sizeof(sockfd_opt)))==NULL) { perror("malloc"); return -1; } //設定監聽套接字選項的回撥函式 p_so->do_task=creat_conn; p_so->fd=fd; //將監聽套接字選項加入到連結串列尾 hash=intHash(fd)&MAXCONN; HashHead[hash].push_back(p_so); // printf("fd1:%d hash1:%d size1:%d\n",fd,hash,HashHead[hash].size()); //向epoll上下文註冊此fd ev.data.fd=fd; ev.events=EPOLLIN; //ev.events=EPOLLIN|EPOLLET //新增此fd ret=epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev); if (ret) bail("epoll_ctl"); return 0; } int main(int argc,char *argv[]) { int listen_fd; //用於監聽的套接字描述符 struct sockaddr_in server; int port; socklen_t optlen; epfd=epoll_create(MAXCONN); //epoll集合 int nev;//epoll_wait返回的檔案描述符個數 vector<struct sockfd_opt*>::iterator it;//迭代器 struct sockfd_opt *p_so; unsigned int hash; port=atoi(argv[1]); if((listen_fd=socket(PF_INET, SOCK_STREAM, 0))==-1) bail("socket()"); setnonblocking(listen_fd); //設定套接字選項 int opt; optlen=sizeof(opt); int ret=setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, optlen); if (ret) bail("setsockopt()"); //伺服器監聽地址準備 memset(&server, 0, sizeof(server)); server.sin_family=PF_INET; server.sin_addr.s_addr=htonl(INADDR_ANY); server.sin_port=htons(port); //繫結伺服器到監聽套接字 if((bind(listen_fd, (struct sockaddr*)&server, sizeof(server)))==-1) bail("bind()"); //開始監聽 if(listen(listen_fd, 5)==-1) bail("listen()"); if (init(listen_fd)) bail("init()"); events=(struct epoll_event*)malloc(sizeof(struct epoll_event)*MAXCONN); printf("server is waiting for acceptance of new client\n"); for (; ; ) { //等待註冊的事件發生 nev=epoll_wait(epfd,events,MAXCONN,-1); if (nev<0) { free(events); bail("epoll_wait"); } for (int i=0; i<nev; i++) { hash=intHash(events[i].data.fd)&MAXCONN; it=HashHead[hash].begin(); while (it!=HashHead[hash].end()) { if ((*it)->fd==events[i].data.fd) { (*it)->do_task(*it); break; //跳出來,迭代器可能會失效(當刪除一個套接字描述符後) } ++it; } } } return 0; }