epoll事件读写

 

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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jyj0710

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值