Linux關於IO複用(epoll模型)
阿新 • • 發佈:2018-12-11
在這篇開始之前,可以檢視前一篇對poll的概念的描述,這樣閱讀起這篇比較不困難。首先我們要知道,epoll模型和前面poll,select是有差別的,他實現的方法不大一樣,我們來看看下面的程式碼,為了和之前的poll,select進行區別,我們依舊採用C/S架構實現。伺服器程式碼不同,客戶端都是一樣的,好的,廢話不多說,先上程式碼,再進行分析。
伺服器程式碼:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/socket.h> #include <netinet/in.h> /* for struct sockaddr_in*/ #include <sys/errno.h> #include <signal.h> #include <sys/select.h> #include <sys/epoll.h> //關於IO複用伺服器的epoll #define LISTEN_SIZE 1024 void error_exit(char *name) { perror(name); exit(-1); } int main(int argc,char *argv[]) { int sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0) { error_exit("create error"); } //繫結地址(ip和埠號) struct sockaddr_in svraddr; memset(&svraddr,0,sizeof(svraddr)); svraddr.sin_family=AF_INET; svraddr.sin_addr.s_addr=INADDR_ANY; //svraddr.sin_addr.s_addr=inet_addr("127.0.0.1");第二種寫法 svraddr.sin_port=htons(5555); int ret=bind(sockfd,(struct sockaddr*)&svraddr,sizeof(svraddr)); if(ret<0) { error_exit("bind error"); } //設定監聽引數back login 半連線數最大 ret=listen(sockfd,1024); if(ret<0) { error_exit("listen error"); } //建立epoll監聽集合 int epfs=epoll_create(100); //新增svrfd struct epoll_event event; event.data.fd=sockfd; event.events=EPOLLIN; epoll_ctl(epfs,EPOLL_CTL_ADD,sockfd,&event); struct epoll_event ret_events[20]={0}; int i=0; struct sockaddr_in removeaddr; memset(&removeaddr,0,sizeof(removeaddr)); int addr_len=sizeof(removeaddr); char buf[1024]={0}; while(1) { int nevent =epoll_wait(epfs,ret_events,20,-1); if(nevent<0) { error_exit("timeout\n"); } else if(nevent==0) { printf("timeout\n"); continue; } for(i=0;i<nevent;i++) { if(ret_events[i].data.fd== sockfd) { int clientfd=accept(sockfd,(struct sockaddr*)&removeaddr,&addr_len); if(clientfd<0) { error_exit("accept error"); } printf("new connection fd %d\n",clientfd); event.data.fd=clientfd; event.events=EPOLLIN; epoll_ctl(epfs,EPOLL_CTL_ADD,clientfd,&event); } else if(ret_events[i].events&EPOLLIN) { int rdsize=read(ret_events[i].data.fd,buf,1024); if(rdsize<=0) { printf("close %d\n",ret_events[i].data.fd); close(ret_events[i].data.fd); event.data.fd=ret_events[i].data.fd; event.events=EPOLLIN; epoll_ctl(epfs,EPOLL_CTL_DEL,ret_events[i].data.fd,&event); } else { printf("read data %s\n",buf); } } } } close(sockfd); }
客戶端依舊是前面一篇的程式碼:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/socket.h> #include <netinet/in.h> /* for struct sockaddr_in*/ #include <sys/errno.h> #include <signal.h> //關於客戶端的socket void error_exit(char *name) { perror(name); exit(-1); } int main(int argc,char *argv[]) { if(argc<3) { printf("run program+ip+port\n"); return-1; } int sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0) { error_exit("create error"); } //連線伺服器,設定伺服器的地址(ip和埠) struct sockaddr_in svraddr; memset(&svraddr,0,sizeof(svraddr)); svraddr.sin_family=AF_INET; svraddr.sin_addr.s_addr= inet_addr(argv[1]); svraddr.sin_port=htons(atoi(argv[2])); int ret =connect(sockfd,(struct sockaddr *)&svraddr,sizeof(svraddr)); if(ret<0) { error_exit("connect error"); } write(sockfd,"hello",strlen("hello")); sleep(5); close(sockfd); return 0; }
先執行服務端,再執行客戶端程式碼:
以下是客戶端結果:
接下來檢視服務端結果:
epoll總結:epoll其實有兩種模式,EPOLLLT和EPOLLET,其實前面我們實現poll,select都是EPOLLLT模式,這篇也是。EPOLLLT為監聽模式,資料在緩衝區讀完,wait還是會觸發。而EPOLLET資料哪怕讀一點點,再次wait就不會觸發,因為ET會從不可讀變為可讀,也只有這一過程wait才會觸發,所以它只能觸發一次。
這時候我們會想,EPOLLET只觸發一次,那我們如何讀取它所有的資料,所以我們實現的時候必須一次性讀乾淨檔案,不然它的檔案描述符就不能用了。所以為了實現這樣的方法,就要對檔案設定為非阻塞。
下面為設定非阻塞程式碼:
void setnonblock(int fd)
{
int flags = 0;
fcntl(fd,F_GETFL,flags);
flags |= O_NONBLOCK;
fcntl(fd,F_SETFL,flags);
}
在設定非阻塞的時候,讀的時候的錯誤必須避免,我們進行判斷,以下是程式碼(errno為錯誤碼,EAGAIN為無資料讀的時候)
int n = 0;
while((nread = read(fd,buf+n,BUFSIZE-n))>0)
{
n+=nread;
}
if(nread == -1 && errno != EAGAIN)
{
perror("read error");
}
還有就是accept的錯誤,以下是程式碼
while(conn_sock=accept(listenfd,(struct sockaddr *)&removeaddr,(size_t *)addr_len))>0)
{
handle_clinet(conn_sock);
}
if(conn_sock == -1)
{
if(errno!=EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
perror("error");
}
最後對select,poll,epoll三種實現方法進行總結:
select 監聽的檔案描述符為固定,哪怕產生一個,也要遍歷全部,效率不高
poll 雖然檔案描述符可以變大,但是是一個數組
epoll 每次讀,只返回讀的數量,效率高(而且ET效率比LT更高,不過ET編碼要求高,而且一次讀就要讀乾淨)