epoll模型

来源:互联网 发布:西门子plc软件tla 编辑:程序博客网 时间:2024/06/05 12:04

linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll

相比于selectepoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,

linux/posix_types.h头文件有这样的声明:

#define __FD_SETSIZE 1024

表示select最多同时监听1024fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。

epoll的接口非常简单,一共就三个函数:

1.int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。但在目前新的内核中,size的大小可以不用管了,不用去定义,可以默认为1,这时候系统会默认读取size的大小

2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

第一个参数是epoll_create()的返回值,

第二个参数表示动作,用三个宏来表示: EPOLL_CTL_ADD:注册新的fdepfd中; EPOLL_CTL_MOD:修改已经注册的fd的监听事件; EPOLL_CTL_DEL:从epfd中删除一个fd

第三个参数是需要监听的fd

第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下: struct epoll_event {

__uint32_t events; /* Epoll events */

epoll_data_t data; /* User data variable */ };

events可以是以下几个宏的集合:

EPOLLIN表示对应的文件描述符可以读(包括对端SOCKET正常关闭); EPOLLOUT表示对应的文件描述符可以写;

EPOLLPRI表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来); EPOLLERR表示对应的文件描述符发生错误;

EPOLLHUP表示对应的文件描述符被挂断;

EPOLLETEPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

3.int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 

等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

man手册中,得到ETLT的具体描述如下

EPOLL事件有两种模型:

Edge Triggered (ET)边缘触发只有数据到来,才触发,不管缓存区中是否还有数据。 Level Triggered (LT)水平触发只要有数据都会触发。

假如有这样一个例子:

1.我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符

2.这个时候从管道的另一端被写入了2KB的数据

3.调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作

4.然后我们读取了1KB的数据

5.调用epoll_wait(2). .... 

下面以我自己写的一个服务端和客户端实现聊天功能为例子:

服务器端:tcp_chat

#include "func.h"void change_noblock(int fd){int status;status=fcntl(fd,F_GETFL);status=status|O_NONBLOCK;int ret=fcntl(fd,F_SETFL,status);if(-1==ret){perror("fcntl");return;}}int main(int argc,char* argv[]){if(argc!=3){printf("error args\n");return -1;}int sfd;sfd=socket(AF_INET,SOCK_STREAM,0);if(-1==sfd){perror("socket");return -1;}struct sockaddr_in ser;memset(&ser,0,sizeof(ser));ser.sin_family=AF_INET;ser.sin_port=htons(atoi(argv[2]));//port转网络字节序ser.sin_addr.s_addr=inet_addr(argv[1]);//IP地址转网络字节序int ret;ret=bind(sfd,(struct sockaddr*)&ser,sizeof(ser));//绑定ip地址和端口号if(-1==ret){perror("bind");return -1;}listen(sfd,10);int epfd=epoll_create(1);struct epoll_event event,evs[3];memset(&event,0,sizeof(event));event.events=EPOLLIN;//描述符是否可读event.data.fd=sfd;ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);if(-1==ret){perror("epoll_ctl");return -1;}memset(&event,0,sizeof(event));event.events=EPOLLIN;//描述符是否可读event.data.fd=STDIN_FILENO;//监控标准输入ret=epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&event);if(-1==ret){perror("epoll_ctl1");return -1;}struct sockaddr_in cli;memset(&cli,0,sizeof(cli));char buf[5]={0};int new_fd=-1;int i;while(1){memset(evs,0,sizeof(evs));ret=epoll_wait(epfd,evs,3,-1);if(ret>0){for(i=0;i<ret;i++){if(evs[i].data.fd==sfd){int addrlen=sizeof(cli);new_fd=accept(sfd,(struct sockaddr*)&cli,&addrlen);if(-1==new_fd){perror("accept");return -1;}printf("client ip=%s,client port=%d\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));change_noblock(new_fd);memset(&event,0,sizeof(event));event.events=EPOLLIN|EPOLLET;//描述符是否可读event.data.fd=new_fd;ret=epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&event);}if(evs[i].data.fd==new_fd){while(1){memset(buf,0,sizeof(buf));ret=recv(new_fd,buf,sizeof(buf),0);if(-1==ret && errno==EAGAIN){break;//读空缓冲区}else if(0==ret){printf("byebye");close(new_fd);memset(&event,0,sizeof(event));event.events=EPOLLIN;//描述符是否可读event.data.fd=new_fd;ret=epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);break;}printf("%s",buf);}printf("\n");}if(evs[i].events==EPOLLIN && evs[i].data.fd==0){memset(buf,0,sizeof(buf));ret=read(STDIN_FILENO,buf,sizeof(buf));if(ret<=0){printf("byebye\n");close(new_fd);memset(&event,0,sizeof(event));event.events=EPOLLIN;//描述符是否可读event.data.fd=new_fd;ret=epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);continue;}ret=send(new_fd,buf,strlen(buf)-1,0);if(-1==ret){perror("send");return -1;}}}}}close(sfd);return 0;}
客户端client_chat:

#include "func.h"int main(int argc,char* argv[]){if(argc!=3){printf("error args\n");return -1;}int sfd;sfd=socket(AF_INET,SOCK_STREAM,0);if(-1==sfd){perror("socket");return -1;}struct sockaddr_in ser;memset(&ser,0,sizeof(ser));ser.sin_family=AF_INET;ser.sin_port=htons(atoi(argv[2]));//port转网络字节序ser.sin_addr.s_addr=inet_addr(argv[1]);//IP地址转网络字节序int ret;ret=connect(sfd,(struct sockaddr*)&ser,sizeof(ser));if(-1==ret){perror("connect");return -1;}char buf[50]={0};fd_set rdset;while(1){FD_ZERO(&rdset);//清空集合FD_SET(STDIN_FILENO,&rdset);FD_SET(sfd,&rdset);ret=select(sfd+1,&rdset,NULL,NULL,NULL);if(ret>0){if(FD_ISSET(STDIN_FILENO,&rdset)){memset(buf,0,sizeof(buf));ret=read(0,buf,sizeof(buf));if(ret<=0){printf("byebye\n");break;}ret=send(sfd,buf,strlen(buf)-1,0);if(-1==ret){perror("send");return -1;}}if(FD_ISSET(sfd,&rdset)){memset(buf,0,sizeof(buf));ret=recv(sfd,buf,sizeof(buf),0);if(-1==ret){perror("recv");return -1;}else if(0==ret){printf("byebye\n");break;}printf("%s\n",buf);}}}close(sfd);return 0;}


0 0
原创粉丝点击