免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 20184 | 回复: 12
打印 上一主题 下一主题

[C] linux的epoll使用ET模式应当如何正确处理EPOLLOUT和EPOLLIN事件? [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-01-25 15:21 |只看该作者 |倒序浏览
hi,all
     最近在开发一个网络游戏服务器,使用epoll的ET模式。看了网上很多的资料,一些基本的概念,
例如accept和recive要到返回EAGAIN或EWOULDBLOCK才停止。
问题:
     如EPOLL的作者Davide Libenzi所说,如果你对一fd同时注册EPOLLIN | EPOLLOUT事件,
即使发送缓冲区并非由满变空,也会触发EPOLLOUT事件(参考链接:http://www.0x61.com/forum/post3712824.html)。

     他指出使用epoll的最好是用ATM模式,当真正需要用到EPOLLOUT时才注册。我理解的ATM模式就是读、写、读、写这样的循环。
像converse兄在一篇epoll笔记中的代码(经过修改):
  1. #include <iostream>
  2. #include <sys/socket.h>
  3. #include <sys/epoll.h>
  4. #include <netinet/in.h>
  5. #include <arpa/inet.h>
  6. #include <fcntl.h>
  7. #include <unistd.h>
  8. #include <stdio.h>
  9. #include <errno.h>

  10. using namespace std;

  11. #define MAXLINE 5
  12. #define OPEN_MAX 100
  13. #define LISTENQ 20
  14. #define SERV_PORT 9980
  15. #define INFTIM 1000

  16. void setnonblocking(int sock)
  17. {
  18.     int opts;
  19.     opts=fcntl(sock,F_GETFL);
  20.     if(opts<0)
  21.     {
  22.         perror("fcntl(sock,GETFL)");
  23.         exit(1);
  24.     }
  25.     opts = opts|O_NONBLOCK;
  26.     if(fcntl(sock,F_SETFL,opts)<0)
  27.     {
  28.         perror("fcntl(sock,SETFL,opts)");
  29.         exit(1);
  30.     }
  31. }

  32. int main()
  33. {
  34.     int i, maxi, listenfd, connfd, sockfd,epfd,nfds;
  35.     ssize_t n;
  36.     char line[MAXLINE];
  37.     socklen_t clilen;
  38.     //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
  39.     struct epoll_event ev,events[20];
  40.     //生成用于处理accept的epoll专用的文件描述符
  41.     epfd=epoll_create(256);
  42.     struct sockaddr_in clientaddr;
  43.     struct sockaddr_in serveraddr;

  44.     listenfd = socket(AF_INET, SOCK_STREAM, 0);
  45.     //把socket设置为非阻塞方式
  46.     setnonblocking(listenfd);
  47.     //设置与要处理的事件相关的文件描述符
  48.     ev.data.fd=listenfd;
  49.     //设置要处理的事件类型
  50.     ev.events=EPOLLIN|EPOLLET;
  51.     //ev.events=EPOLLIN;
  52.     //注册epoll事件
  53.     epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
  54.     bzero(&serveraddr, sizeof(serveraddr));
  55.     serveraddr.sin_family = AF_INET;
  56.     char *local_addr="0.0.0.0";
  57.     inet_aton(local_addr,&(serveraddr.sin_addr));//htons(SERV_PORT);
  58.     serveraddr.sin_port=htons(SERV_PORT);
  59.     bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
  60.     listen(listenfd, LISTENQ);
  61.     maxi = 0;
  62.     for ( ; ; ) {
  63.         static int last_fd = 0;
  64.         char empty[10240];
  65.         send(last_fd, empty, sizeof(empty), MSG_DONTWAIT);
  66.         //等待epoll事件的发生
  67.         nfds=epoll_wait(epfd,events,20,500);
  68.         //处理所发生的所有事件
  69.         for(i=0;i<nfds;++i)
  70.         {
  71.             if(events[i].data.fd==listenfd)
  72.             {
  73.                 connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
  74.                 if(connfd<0){
  75.                     perror("connfd<0");
  76.                     exit(1);
  77.                 }
  78.                 //setnonblocking(connfd);
  79.                 char *str = inet_ntoa(clientaddr.sin_addr);
  80.                 cout << "accapt a connection from " << str << endl;
  81.                 //设置用于读操作的文件描述符
  82.                 ev.data.fd=connfd;
  83.                 //设置用于注测的读操作事件
  84.                 ev.events=EPOLLIN|EPOLLET;
  85.                 //ev.events=EPOLLIN;
  86.                 //注册ev
  87.                 epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
  88.             }
  89.             else if(events[i].events&EPOLLIN)
  90.             {

  91.                 cout << "EPOLLIN" << endl;
  92.                 if ( (sockfd = events[i].data.fd) < 0)
  93.                     continue;
  94.                 if ( (n = read(sockfd, line, MAXLINE)) < 0) {
  95.                     if (errno == ECONNRESET) {
  96.                         close(sockfd);
  97.                         events[i].data.fd = -1;
  98.                     } else
  99.                         std::cout<<"readline error"<<std::endl;
  100.                 } else if (n == 0) {
  101.                     close(sockfd);
  102.                     events[i].data.fd = -1;
  103.                 }
  104.                 line[n] = '\0';
  105.                 cout << "read " << line << endl;
  106.                 //设置用于写操作的文件描述符
  107.                 ev.data.fd=sockfd;
  108.                 //设置用于注测的写操作事件
  109.                 ev.events=EPOLLOUT|EPOLLET;
  110.                 //修改sockfd上要处理的事件为EPOLLOUT
  111.                 epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
  112.                 last_fd = sockfd;
  113.             }
  114.             else if(events[i].events&EPOLLOUT)
  115.             {
  116.                 sockfd = events[i].data.fd;
  117.                 write(sockfd, line, n);
  118.                 //设置用于读操作的文件描述符
  119.                 ev.data.fd=sockfd;
  120.                 //设置用于注测的读操作事件
  121.                 ev.events=EPOLLIN|EPOLLET;
  122.                 //修改sockfd上要处理的事件为EPOLIN
  123.                 epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
  124.             }
  125.         }
  126.     }
  127.     return 0;
  128. }
复制代码
不过,假如发送缓冲区一直满,那就不会触发EPOLLOUT事件,那就不会切换到读取模式,那读出的数据就会变慢了。

请问正确处理这两个事件的框架是怎样的呢?

论坛徽章:
0
2 [报告]
发表于 2011-01-25 22:39 |只看该作者
我对此表示十分关注

论坛徽章:
0
3 [报告]
发表于 2011-01-25 23:12 |只看该作者
你知道的也就你说的这么用了。。囧。。。。

论坛徽章:
324
射手座
日期:2013-08-23 12:04:38射手座
日期:2013-08-23 16:18:12未羊
日期:2013-08-30 14:33:15水瓶座
日期:2013-09-02 16:44:31摩羯座
日期:2013-09-25 09:33:52双子座
日期:2013-09-26 12:21:10金牛座
日期:2013-10-14 09:08:49申猴
日期:2013-10-16 13:09:43子鼠
日期:2013-10-17 23:23:19射手座
日期:2013-10-18 13:00:27金牛座
日期:2013-10-18 15:47:57午马
日期:2013-10-18 21:43:38
4 [报告]
发表于 2011-01-26 08:45 |只看该作者
如果你来逻辑是 接收-》处理-》发送,发送来不及了,接收了数据处理也没用,还得等着发送

论坛徽章:
0
5 [报告]
发表于 2011-01-26 10:28 |只看该作者
如果你来逻辑是 接收-》处理-》发送,发送来不及了,接收了数据处理也没用,还得等着发送
hellioncu 发表于 2011-01-26 08:45



    发送时除了内核的缓冲区以外  我自己也有一个缓冲区,动态增长的,所以暂时发不出去也没问题
    但如果我接收慢了的话,数据会不会丢失呢?

    我看了一下ngix的epoll模块,对于EPOLLOUT和EPOLLIN,他们都是在需要的时候才注册的。两个可能两个同时注册,同时注册时其实就多几次系统调用。

论坛徽章:
324
射手座
日期:2013-08-23 12:04:38射手座
日期:2013-08-23 16:18:12未羊
日期:2013-08-30 14:33:15水瓶座
日期:2013-09-02 16:44:31摩羯座
日期:2013-09-25 09:33:52双子座
日期:2013-09-26 12:21:10金牛座
日期:2013-10-14 09:08:49申猴
日期:2013-10-16 13:09:43子鼠
日期:2013-10-17 23:23:19射手座
日期:2013-10-18 13:00:27金牛座
日期:2013-10-18 15:47:57午马
日期:2013-10-18 21:43:38
6 [报告]
发表于 2011-01-26 10:33 |只看该作者
发送时除了内核的缓冲区以外  我自己也有一个缓冲区,动态增长的,所以暂时发不出去也没问题
   ...
future0906 发表于 2011-01-26 10:28



    又不是UDP,不会丢

论坛徽章:
0
7 [报告]
发表于 2011-01-26 10:49 |只看该作者
回复 6# hellioncu


    那你的意思就是说上面的EPOLLOUT->EPOLLIN->EPOLLOUT的模式是没问题的 是吗?

论坛徽章:
324
射手座
日期:2013-08-23 12:04:38射手座
日期:2013-08-23 16:18:12未羊
日期:2013-08-30 14:33:15水瓶座
日期:2013-09-02 16:44:31摩羯座
日期:2013-09-25 09:33:52双子座
日期:2013-09-26 12:21:10金牛座
日期:2013-10-14 09:08:49申猴
日期:2013-10-16 13:09:43子鼠
日期:2013-10-17 23:23:19射手座
日期:2013-10-18 13:00:27金牛座
日期:2013-10-18 15:47:57午马
日期:2013-10-18 21:43:38
8 [报告]
发表于 2011-01-26 10:56 |只看该作者
回复  hellioncu


    那你的意思就是说上面的EPOLLOUT->EPOLLIN->EPOLLOUT的模式是没问题的 是吗?
future0906 发表于 2011-01-26 10:49



    ET模式accept、read等要循环直到失败。

实际上我觉得还是LT模式好用,没觉得性能有啥不好

论坛徽章:
1
申猴
日期:2014-02-11 14:50:31
9 [报告]
发表于 2011-01-26 13:24 |只看该作者
为什么要EPOLLOUT->EPOLLIN->EPOLLOUT?业务逻辑需要?

论坛徽章:
0
10 [报告]
发表于 2011-02-01 16:45 |只看该作者
具体什么逻辑是开发决定的。

                //设置用于写操作的文件描述符

                ev.data.fd=sockfd;

                      //设置用于注测的写操作事件。这里是开发者决定的,如果没有不关注写操作,只关注读,这里可以接着修改为监听读服务。
              /*
epoll_wait运行的原理是
等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。
并 且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。
              */

                ev.events=EPOLLOUT|EPOLLET;

                 //修改sockfd上要处理的事件为EPOLLOUT

                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);

楼主给的代码是比较简单的。
ET方式,读必须判断是不是都读完了。
写必须要关注返回值,当返回值小于实际要发送的数量,说明该id已经不能写了,所以就要设置监听这个端口可写。当触发可写的时候把没发送完的数据发送完。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP