###epoll簡介:
epoll是Linux下多路複用IO接口select/poll的加強版本,它能顯著提升程序在大量併發鏈接中只有少許活躍的狀況下的系統CPU利用率,由於它會複用文件描述符集合來傳遞結果而不用迫使開發者每次等待事件以前都必須從新準備要被偵聽的文件描述符集合,另外一點緣由就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就好了。
目前epell是linux大規模併發網絡程序中的熱門首選模型。
epoll除了提供select/poll那種IO事件的電平觸發(Level Triggered)外,還提供了邊沿觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減小epoll_wait/epoll_pwait的調用,提升應用程序效率。
能夠使用cat命令查看一個進程能夠打開的socket描述符上限。javascript
cat /proc/sys/fs/file-max
若有須要,能夠經過修改配置文件的方式修改該上限值。html
sudo vi /etc/security/limits.conf 在文件尾部寫入如下配置,soft軟限制,hard硬限制。以下圖所示。 * soft nofile 65536 * hard nofile 100000
###基礎APIjava
epoll_createlinux
建立一個epoll句柄,參數size用來告訴內核監聽的文件描述符的個數,跟內存大小有關。 #include <sys/epoll.h> int epoll_create(int size) size:監聽數目 調用以後內核建立一顆紅黑樹模型
epoll_ctlweb
控制某個epoll監控的文件描述符上的事件:註冊、修改、刪除。 在系統內核生成的紅黑樹上進行修改 #include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) epfd: 爲epoll_creat的句柄 op: 表示動做,用3個宏來表示: EPOLL_CTL_ADD (註冊新的fd到epfd), EPOLL_CTL_MOD (修改已經註冊的fd的監聽事件), EPOLL_CTL_DEL (從epfd刪除一個fd); event: 告訴內核須要監聽的事件 struct epoll_event { _uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; EPOLLIN : 表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉) EPOLLOUT: 表示對應的文件描述符能夠寫 EPOLLPRI: 表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來) EPOLLERR: 表示對應的文件描述符發生錯誤 EPOLLHUP: 表示對應的文件描述符被掛斷; EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)而言的 EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏
epoll_wait緩存
等待所監控文件描述符上有事件的產生,相似於select()調用。 #include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) events: 用來存內核獲得事件的集合, maxevents: 告以內核這個events有多大,這個maxevents的值不能大於建立epoll_create()時的size, timeout:是超時時間 -1: 阻塞 0: 當即返回,非阻塞 >0: 指定毫秒 返回值: 成功返回有多少文件描述符就緒,時間到時返回0,出錯返回-1
###樣例:
爲了使代碼更加簡潔,將經常使用的函數進行了二次封裝,加入了出錯處理,見wrap.c.服務器
/************************************************************************* > File Name: server.c > Author: sunxingying > Mail: 1159015605@qq.com > Created Time: 2017年06月29日 星期四 03時36分35秒 ************************************************************************/ #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<string.h> #include<sys/epoll.h> #include"wrap.h" #define _SIZE_ 128 #define OPEN_MAX 256 typedef struct epoll_msg { int fd; char buf[_SIZE_]; }epoll_t,*epoll_p,**epoll_pp; static void *allocator(int fd) { epoll_p buf=(epoll_p)malloc(sizeof(epoll_t)); if(NULL==buf) { perror("malloc"); exit(6); } buf->fd=fd; return buf; } void delalloc(void *ptr) { if(NULL!=ptr) { free(ptr); } } int startup(char *ip,int port) { int sock=Socket(AF_INET,SOCK_STREAM,0); int opt=1; if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0) { perror("setsockopt"); exit(1); } struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(port); local.sin_addr.s_addr=inet_addr(ip); Bind(sock,(struct sockaddr*)&local,sizeof(local)); Listen(sock,10); return sock; } int main(int argc,char *argv[]) { if(argc!=3) { printf("請輸入ip地址和端口號\n"); return 1; } int lis_sock=startup(argv[1],atoi(argv[2])); //建立epoll模型,epfd指向紅黑數跟節點 int epfd=epoll_create(OPEN_MAX); if(epfd<0) { perror("epoll_create"); } struct epoll_event envs; envs.events=EPOLLIN|EPOLLET; envs.data.ptr=allocator(lis_sock); //將lis_sock及對應的結構體設置到樹上,epfd可找到該樹 epoll_ctl(epfd,EPOLL_CTL_ADD,lis_sock,&envs); while(1) { int num=0; int timeout=-1; struct epoll_event evs[32]; int max=32; switch((num=epoll_wait(epfd,evs,max,timeout))) { case 0: printf("time out..."); break; case -1: perror("epoll _wait"); break; default: { int i=0; for(;i<num;i++) { int fd=((epoll_p)(evs[i].data.ptr))->fd; if(fd==lis_sock&&evs[i].events&EPOLLIN) { struct sockaddr_in peer; socklen_t len=sizeof(peer); int connfd=Accept(lis_sock,(struct sockaddr*)&peer,&len); envs.events=EPOLLIN; envs.data.ptr=allocator(connfd); epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&envs); } else if(fd!=lis_sock&&evs[i].events&EPOLLIN) { int s=read(fd,((epoll_p)(evs[i].data.ptr))->buf,_SIZE_-1); if(s>0) { char *buf=((epoll_p)(evs[i].data.ptr))->buf; buf[s]=0; printf("client # %s\n",buf); evs[i].events=EPOLLOUT; epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&evs[i]); } else if(s==0) { epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL); delalloc(evs[i].data.ptr); close(fd); } else { perror("read"); } } else if(fd!=lis_sock&&evs[i].events&EPOLLOUT) { char *msg="http/1.0 200 ok\r\n\r\n<html><title>孫興穎的epoll服務器</title><h1>歡迎來到孫興穎的epoll服務器</h1><p>當前時間是:</p><script type=\"text/javascript\">document.write(Date())</script></html>"; write(fd,msg,strlen(msg)); delalloc(evs[i].data.ptr); epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&evs[i]); close(fd); } } } } } return 0; }
wrap.c網絡
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <sys/socket.h> void perr_exit(const char *s) { perror(s); exit(-1); } int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) { int n; again: //accept是慢速系統調用,有可能在阻塞期間被信號中斷 //被信號中斷時errno 被設置爲ECONNABORTED或者EINTR if ((n = accept(fd, sa, salenptr)) < 0) { if ((errno == ECONNABORTED) || (errno == EINTR)) goto again; else perr_exit("accept error"); } return n; } int Bind(int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = bind(fd, sa, salen)) < 0) perr_exit("bind error"); return n; } int Connect(int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = connect(fd, sa, salen)) < 0) perr_exit("connect error"); return n; } int Listen(int fd, int backlog) { int n; if ((n = listen(fd, backlog)) < 0) perr_exit("listen error"); return n; } int Socket(int family, int type, int protocol) { int n; if ((n = socket(family, type, protocol)) < 0) perr_exit("socket error"); return n; } ssize_t Read(int fd, void *ptr, size_t nbytes) { ssize_t n; //read也是慢速系統調用 //read返回值: /* * 1. >0實際讀到的字節數 buf=1024 1.==buf 1024 2. <buf 56 * 2. ==0讀到了(文件、管道、socket末尾了--對端關閉 * 3. -1 異常 * 1. errno==EINTR 被信號中斷 重啓/quit * 2. error==EAGAIN(EWOULDBLOCK)非阻塞方式讀,而且沒有數據 * 3. 其餘值 出現錯誤 --perror exit * */ again: if ( (n = read(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again; else return -1; } return n; } ssize_t Write(int fd, const void *ptr, size_t nbytes) { ssize_t n; //write也是慢速系統調用也會遇到同上問題, again: if ( (n = write(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again; else return -1; } return n; } int Close(int fd) { int n; if ((n = close(fd)) == -1) perr_exit("close error"); return n; } /*參三: 應該讀取的字節數*/ ssize_t Readn(int fd, void *vptr, size_t n) { size_t nleft; //usigned int 剩餘未讀取的字節數 ssize_t nread; //int 實際讀到的字節數 char *ptr; ptr = vptr; nleft = n; /* *假設socket有4096字節,而以太網幀格式最大一次是1500 *一個數據包就發不完了,readn(cfd,buf,4096); *nleft是剩餘還未讀的字節數,nleft=4096-1500, *讀完指針向後偏移 */ while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else return -1; } else if (nread == 0) break; nleft -= nread; ptr += nread; } return n - nleft; } /*writen同readn*/ ssize_t Writen(int fd, const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; else return -1; } nleft -= nwritten; ptr += nwritten; } return n; } static ssize_t my_read(int fd, char *ptr) { static int read_cnt; static char *read_ptr; static char read_buf[100]; if (read_cnt <= 0) { again: if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { if (errno == EINTR) goto again; return -1; } else if (read_cnt == 0) return 0; read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; return 1; } /*readline ---fgets*/ //傳出參數vptr ssize_t Readline(int fd, void *vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c)) == 1) { *ptr++ = c; if (c == '\n') break; } else if (rc == 0) { *ptr = 0; return n - 1; } else return -1; } *ptr = 0; return n; }
wrap.h併發
#ifndef __WRAP_H_ #define __WRAP_H_ void perr_exit(const char *s); int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); int Bind(int fd, const struct sockaddr *sa, socklen_t salen); int Connect(int fd, const struct sockaddr *sa, socklen_t salen); int Listen(int fd, int backlog); int Socket(int family, int type, int protocol); ssize_t Read(int fd, void *ptr, size_t nbytes); ssize_t Write(int fd, const void *ptr, size_t nbytes); int Close(int fd); ssize_t Readn(int fd, void *vptr, size_t n); ssize_t Writen(int fd, const void *vptr, size_t n); ssize_t my_read(int fd, char *ptr); ssize_t Readline(int fd, void *vptr, size_t maxlen); #endif
###效果展現:
異步