spacer.gif实验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 记录错误号

调用参数:

epfdepoll_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 上的可读和可写事件*/




spacer.gifevent.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



spacer.gif#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++)



spacer.gif{

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

水平触发模式下,以读模式为例,只要被监视的套接字接收缓存中有可读数据,



spacer.gifepoll_wait  立刻返回。边沿触发,只有当套接字缓存收到了数据时,epoll_wait

才会返回,即使缓存中还有上次没有读完的数据。