实验10 事件I/O |
1 事件I/O select 函数的局限性 a)进程所能同时打开的文件描述符个数受FD_SETSIZE 大小的限制 b)每当select 函数返回可用的文件描述符集合后,应用都不得不对所有已 注册的文件描述符进行遍历比对,以确定哪个描述符上发生了事件,从而对其进 行读写操作 |
2 要掌握的函数 Epoll 通过三个系统调用完成了高效的I/O 模型的实现 epoll_create,初始化epoll 上下文环境 epoll_ctl,向epoll 上下文中添加或去除需要系统监视的文件描述符 epoll_wait,等待文件描述符上发生事件 2.1 epoll_create 函数 int epoll_create (int size) 返回值:文件描述符表示成功,-1 表示错误,errno 记录错误号 例如: int epfd; epfd = epoll_create (100); if (epfd < 0) perror ("epoll_create"); |
2.2 epoll_ctl 函数 /* 向epoll 环境中添加,删除,改变要监视的文件描述符*/ int epoll_ctl (int epfd, int op, int fd,struct epoll_event *event); 返回值:0 表示成功,-1 表示错误,errno 记录错误号 调用参数: epfd,epoll_create 创建的epoll 环境句柄 op,规定对fd 的操作方式 event,事件 |
2.3 函数epoll_wait #include <sys/epoll.h> int epoll_wait (int epfd, struct epoll_event *events,int maxevents, int timeout); 返回值:发生事件的文件描述符个数表示成功,-1 表示错误,errno 记录错误号 |
3 程序的具体过程: 3.1 注册文件描述符到epoll struct epoll_event event; int ret; /* 将来epoll 会返回此fd 给应用*/ event.data.fd = fd; /* 监视此fd 上的可读和可写事件*/ |
event.events = EPOLLIN | EPOLLOUT; ret = epoll_ctl (epfd, EPOLL_CTL_ADD, fd, &event); if (ret) perror ("epoll_ctl"); |
3.2 修改epoll 监视事件 struct epoll_event event; int ret; /* 将来epoll 会返回此fd 给应用*/ event.data.fd = fd; /* 监视此fd 上的可读事件*/ event.events = EPOLLIN; ret = epoll_ctl (epfd, EPOLL_CTL_MOD, fd, &event); if (ret) perror ("epoll_ctl"); |
3.3 取消epoll 监视的文件描述符 struct epoll_event event; int ret; ret = epoll_ctl (epfd, EPOLL_CTL_DEL, fd, &event); if (ret) perror ("epoll_ctl"); |
3.4 等待epoll 事件发生,随后变量events 事件 int nr_events = epoll_wait (epfd, events, MAX_EVENTS, -1); /* 对epoll 返回的nr_events 个事件依次遍历,进行处理*/ for (i = 0; i < nr_events; i++) { printf ("event=%ld on fd=%d\n", events[i].events,events[i].data.fd); /* * 处理events[i].data.fd 文件描述符. */ } |
4 程序示例 #include <sys/socket.h> #include <sys/epoll.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <errno.h> #define BUFSIZE 512 #define OPEN_MAX 100 |
#define PORT 5001 #define MAX_EVENTS 20 char buf[BUFSIZE]; struct epoll_event events[MAX_EVENTS]; void setnonblocking(int sock) { int flag; fcntl(sock,F_GETFL,&flag); fcntl(sock,F_SETFL,flag|O_NONBLOCK); }; int main() { |
int listenfd=socket(AF_INET,SOCK_STREAM,0); setnonblocking(listenfd); |
struct sockaddr_in cli,svr; inet_aton("0.0.0.0",&svr.sin_addr); svr.sin_port=htons(PORT); svr.sin_family=AF_INET; |
//产生epoll 实例的文件描述符 int efd=epoll_create(256); |
//设置epoll 事件,将fd 加入ev.data.fd 是为了返回的时候知道是哪个套接字 可读或者可写 struct epoll_event ev; ev.data.fd=listenfd; ev.events=EPOLLIN|EPOLLET; |
//注册epoll 事件,将在listenfd(第3 个参数上)检测,如果有事件,会返回 ev,注意ev 已经拷贝到了内核,可以重新使用ev 注册其他事件 epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&ev); |
int br=bind(listenfd,(struct sockaddr*)&svr,sizeof svr); if(br<0){printf("error bind!\r\n");exit(-1);}; listen(listenfd,20); while(1) { int nev=epoll_wait(efd,events,MAX_EVENTS,-1); if(nev<0){printf("error epoll_wait!\r\n");exit(-1);}; int i=0; for(i=0;i<nev;i++) |
{ |
if(listenfd==events[i].data.fd) { //监听套接字可读 int len=sizeof cli; int confd=accept(listenfd,(struct sockaddr*)&cli,&len); if(confd<=0){printf("error!\r\n");exit(-1);}; printf("servergotaconnection %s:%d\r\n",inet_ntoa(cli.sin_addr),htons(cli.sin_port)); setnonblocking(confd); ev.data.fd=confd; ev.events==EPOLLIN|EPOLLET; epoll_ctl(efd,EPOLL_CTL_ADD,confd,&ev); continue; } if(events[i].events&EPOLLIN) { int n=read(events[i].data.fd,buf,BUFSIZE); if((n<0)&&(errno!=ECONNRESET)) { printf("error in read!\r\n"); exit(-1); } if(n<=0) { close(events[i].data.fd); |
epoll_ctl(efd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]); events[i].data.fd=-1; } else { buf[n]=0; printf("%s\r\n",buf); } |
}//end of if 是E_POLLIN }//end of for }//end of while |
} |
from |
5 边沿触发ET 和水平触发LT 水平触发模式下,以读模式为例,只要被监视的套接字接收缓存中有可读数据, |
则epoll_wait 立刻返回。边沿触发,只有当套接字缓存收到了数据时,epoll_wait 才会返回,即使缓存中还有上次没有读完的数据。 |
转载于:https://blog.51cto.com/wolfword/1240346