EPOLL模型总结2010-10-17 19:20:21| 分类: 网络编程 | 标签:学习 |字号大中小 订阅 .
在Linux网络编程中,很长时间都是在使用select模型做事件触发,现在Linux在内核中加了一种新的机制,即EPOLL模型。相比与select,EPOLL最大的好处在于不会随着FD的增加而线性的降低效率。而select在内核中的实现方式为轮询集合中的每一个FD。FD越多消耗的时间自然越大。并且内核中select允许的最大FD数为1024,修改必须重新编译内核,自然比较麻烦。
一.EPOLL常用的函数
1.int epoll_create(int size);
创建一个EPOLL句柄,size用来告诉内核监听的最大数目,不同与select的第一个参数(最大FD+1),需要注意的是在退出的时候,必须调用close关闭句柄,否则可能导致FD耗尽。
2.int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
epoll事件注册函数,它不同于select是在监听时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是调用epoll_create创建的epoll句柄。
第二个参数是操作类型。
EPOLL_CTL_ADD:添加新的事件到epfd。
EPOLL_CTL_MOD:修改epfd中的事件。
EPOLL_CTL_DEL:删除epfd中的事件。
第三个参数是要监听的fd。
第四个参数告诉内核要监听什么事件。struct event结构如下:
struct epoll_event{
__uint32_t events;
epoll_data_t data;
};
events可以是下面几个宏的集合:
EPOLLIN:表示对应的文件描述符可以读或者对端的socket正常关闭。
EPOLLOUT:表示对应的描述符可以写。
EPOLLPRI:表示对应的文件描述符有紧急数据需要读。
EPOLLERR:表示对应的文件描述符发生错误。
EPOLLHUP:表示对应的文件描述符被挂断。
EPOLLET:表示将触发方式设置为边缘触发(ET),系统默认为水平触发(LT)。
EPOLLONESHOT:表示只监听一次,下次需要监听需重新加入epfd。
3.int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);
等待事件的发生,类似与select调用,events用来从内核得到发生的事件,maxevents告诉内核这个事件有多大,且不能大于epoll_create的参数size;timeout为超时时间,0为立即返回。该函数返回需要处理的事件数,返回0表示超时。
二.EPOLL触发的两种方式
1.水平触发(LT,level,triggered)
LT是EPOLL的缺省工作模式,并同时支持阻塞和非阻塞模式。在这种触发方式中,内核告诉你一个fd是否就绪了,然后你可以对就绪的fd进行IO操作。如果你不做任何处理,内核还会继续通知你。所以,这种模式出错的可能小一点,传统的select/poll模型就是采用这种方式。
2.边缘触发(ET,edge triggered)
ET是高速触发模式,只支持非阻塞的工作模式。在这种模式下,当fd从未就绪变为就绪时,内核回通过epoll通知你。然后它会假设你知道fd已经就绪,并做了相应的处理,如果你不处理或处理不完全内核不会再发通知,一直等到下一个事件的到来。所以,当工作在ET模式下时,recv到的数据大小恰好等于请求的数据大小时,缓存区内可能还有数据没有读完。也意味着这次事件还没有处理完,需要循环接收:
while (rs)
{
buflen = recv(activeevents[i].data.fd,buf,sizeof(buf),0);
if (buflen < 0)
{
if (errno == EAGAIN)//表示数据已经读完
break;
else
return;
}else if(0 == buflen)
{
//socket已经正常关闭
}
if (buflen == sizeof(buf))//数据可能还没读完
{
rs = 1;
}else
{
rs = 0;
}
}
还有,假设发送流量大于接收流量,由于采用的是非阻塞的模式,send()虽然返回,但数据并未被接收端成功接收。这样不断的读与发,到缓存区满了会产生EAGAIN错误,同时不理会这次请求发送的数据。所以需要socket_send()函数专门处理这种情况,尽量等到数据发送完毕后返回,返回-1表示错误。在socket_send内部,当发送缓存区已满(send 返回-1,errno为EAGAIN)时,等待一段时间后重新发送。
ssize_t socket_send(int sockfd,const char* buffer,int buflen)
{
ssize_t tmp;
size_t total = buflen;
const char* p = buffer;
while (1)
{
tmp = send(sockfd,p,total,0);
if (tmp < 0)
{
if (errno == EINTR)
{
return -1;
}
if (errno == EAGAIN)//缓存区已满,等待后再发
{
usleep(1000);
continue;
}
return -1;
}
if ((size_t)tmp == total)
return buflen;
total -= tmp;
p += tmp;
}
return tmp;
}