免费注册 查看新帖 |

Chinaunix

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

[C] epoll 若干疑问 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-05-20 22:32 |只看该作者 |倒序浏览
首先引用一个服务器端 epoll 的实例。原帖地址:http://blog.chinaunix.net/u/16292/showart_1844376.html

[/code]

#include <iostream>
#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>

using namespace std;

#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000

void setnonblocking(int sock)
{
    int opts;
    opts=fcntl(sock,F_GETFL);
    if(opts<0)
    {
        perror("fcntl(sock,GETFL)");
        exit(1);
    }
    opts = opts|O_NONBLOCK;
    if(fcntl(sock,F_SETFL,opts)<0)
    {
        perror("fcntl(sock,SETFL,opts)");
        exit(1);
    }
}

int main(int argc, char* argv[])
{
    int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
    ssize_t n;
    char line[MAXLINE];
    socklen_t clilen;


    if ( 2 == argc )
    {
        if( (portnumber = atoi(argv[1])) < 0 )
        {
            fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
            return 1;
        }
    }
    else
    {
        fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
        return 1;
    }



    //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件

    struct epoll_event ev,events[20];
    //生成用于处理accept的epoll专用的文件描述符

    epfd=epoll_create(256);
    struct sockaddr_in clientaddr;
    struct sockaddr_in serveraddr;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    //把socket设置为非阻塞方式

    //setnonblocking(listenfd);

    //设置与要处理的事件相关的文件描述符

    ev.data.fd=listenfd;
    //设置要处理的事件类型

    ev.events=EPOLLIN|EPOLLET;
    //ev.events=EPOLLIN;

    //注册epoll事件

    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    char *local_addr="127.0.0.1";
    inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber);

    serveraddr.sin_port=htons(portnumber);
    bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
    listen(listenfd, LISTENQ);
    maxi = 0;
    for ( ; ; ) {
        //等待epoll事件的发生

        nfds=epoll_wait(epfd,events,20,500);
        //处理所发生的所有事件

        for(i=0;i<nfds;++i)
        {
            if(events.data.fd==listenfd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。

            {
                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
                if(connfd<0){
                    perror("connfd<0");
                    exit(1);
                }
                //setnonblocking(connfd);

                char *str = inet_ntoa(clientaddr.sin_addr);
                cout << "accapt a connection from " << str << endl;
                //设置用于读操作的文件描述符

                ev.data.fd=connfd;
                //设置用于注测的读操作事件

                ev.events=EPOLLIN|EPOLLET;
                //ev.events=EPOLLIN;

                //注册ev

                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }
            else if(events.events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。

            {
                cout << "EPOLLIN" << endl;
                if ( (sockfd = events.data.fd) < 0)
                    continue;
                if ( (n = read(sockfd, line, MAXLINE)) < 0) {
                    if (errno == ECONNRESET) {
                        close(sockfd);
                        events.data.fd = -1;
                    } else
                        std::cout<<"readline error"<<std::endl;
                } else if (n == 0) {
                    close(sockfd);
                    events.data.fd = -1;
                }
                line[n] = '\0';
                cout << "read " << line << endl;
                //设置用于写操作的文件描述符

                ev.data.fd=sockfd;
                //设置用于注测的写操作事件

                ev.events=EPOLLOUT|EPOLLET;
                //修改sockfd上要处理的事件为EPOLLOUT

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

            }
            else if(events.events&EPOLLOUT) // 如果有数据发送

            {
                sockfd = events.data.fd;
                write(sockfd, line, n);
                //设置用于读操作的文件描述符

                ev.data.fd=sockfd;
                //设置用于注测的读操作事件

                ev.events=EPOLLIN|EPOLLET;
                //修改sockfd上要处理的事件为EPOLIN

                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
            }
        }
    }
    return 0;
}

[/code]

问题如下:
1. 网上有人说 “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 重新设定 fd 的事件类型?man epoll_wait 好像没有提到。

2. 上面这个例子中,不知道什么可以触发 EPOLLOUT 事件?

3. 同样是这个例子,//epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); 这一行为什么要注释掉?

4. 还是这个例子,为什么 EPOLLIN 或 EPOLLOUT 事件触发后,事件类型为 EPOLLIN 的 fd 事件类型要改为 EPOLLOUT , 而原来为 EPOLLOUT 却要改为 EPOLLIN ?

5. 因为每次循环都要传递给 epoll_wait,events 数组不需要清零吗?

论坛徽章:
0
2 [报告]
发表于 2009-05-20 23:01 |只看该作者
1,不是
2,用的是边缘触发方式,所以只有在写未就绪变成写就绪的时候,进程会受到EPOLLOUT事件
3,你自己去掉试试呗,我也不知道,估计是代码贴错了,哈哈
4,程序的意思是这种服务器和客户端双发的读写是交互的,读-->写-->读-->写……
5,不需要

论坛徽章:
0
3 [报告]
发表于 2009-05-20 23:17 |只看该作者

回复 #2 lenky0401 的帖子

> 1,不是
这个你确定?

> 2,用的是边缘触发方式,所以只有在写未就绪变成写就绪的时候,进程会受到EPOLLOUT事件
这个我知道,我想知道什么时候就写就绪了?EPOLLIN 被触发的时候,EPOLLOUT 也会被触发?但是一开始只设置了 EPOLLIN,并没有设置 EPOLLOUT 。

> 4,程序的意思是这种服务器和客户端双发的读写是交互的,读-->写-->读-->写……
你的意思是服务端收到数据后,就立即将收到的数据写回给客户端?如果是这样,可以收到数据后直接写就可以了啊,为什么还要列一个分支  “else if(events.events&EPOLLOUT) // 如果有数据发送 ” ?

论坛徽章:
0
4 [报告]
发表于 2009-05-20 23:21 |只看该作者

回复 #2 lenky0401 的帖子

1. 网上有人说 “epoll_wait运行的原理是 等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。 并且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型”

也就是说,上面这段话是错的,原来的设置还在,根本不需要重新设置?

论坛徽章:
1
天蝎座
日期:2013-08-25 10:27:22
5 [报告]
发表于 2009-05-21 09:01 |只看该作者
1、那个说法本身就是矛盾的,前边说“清空”,那就没有事件了,后边又说“EPOLL_CTL_MOD”,这个MOD是改变的意思。。既然清空,又何来改变?

EPOLL_CTL_ADD
                     Add the target file descriptor fd to the epoll descriptor epfd and associate the  event  event  with  the  internal  file
                     linked to fd.

              EPOLL_CTL_MOD
                     Change the event event associated with the target file descriptor fd.

              EPOLL_CTL_DEL
                     Remove the target file descriptor fd from the epoll file descriptor, epfd.  The event is ignored and can be NULL (but see
                     BUGS below).

这三个操作的用法要清楚。


2、就这个代码来说,是不会触发EPOLLOUT的。

  1. ev.events=EPOLLOUT|EPOLLET;
  2. //修改sockfd上要处理的事件为EPOLLOUT

  3. //epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
复制代码

这里虽然在触发EPOLLIN事件后,将sockfd的事件彷佛要改成EPOLLOUT|EPOLLET,
但是epoll_ctl被注释掉了,所以事件没变。epoll仍然只监听这个套接字的读事件。



另外4、
你的意思是服务端收到数据后,就立即将收到的数据写回给客户端?如果是这样,可以收到数据后直接写就可以了啊,为什么还要列一个分支  “else if(events.events&EPOLLOUT) // 如果有数据发送 ” ?

可以读到后直接写,但是有时候缓冲区是满的,直接写返回EAGAIN。

[ 本帖最后由 yangsf5 于 2009-5-21 09:02 编辑 ]

论坛徽章:
0
6 [报告]
发表于 2009-05-21 09:22 |只看该作者
epoll跟select不同,select需要重新设置,epoll不需要,只要你不del。

论坛徽章:
0
7 [报告]
发表于 2009-05-21 11:44 |只看该作者
没太细看,粗粗浏览了一下lz列举的代码,感觉至少有两个或以上的问题。

论坛徽章:
0
8 [报告]
发表于 2009-05-21 23:34 |只看该作者

回复 #6 cugb_cat 的帖子

高并发性环境下,epoll 的ET 模型 的优势到底体现在什么地方? 这个问题一直想不明白。
ET 与 LT 的区别是很清楚了,但看不出有什么优势。能否举个形象一点的例子?
谢谢了。

论坛徽章:
0
9 [报告]
发表于 2009-06-21 19:39 |只看该作者
ET模式下,在得到EPOLLIN通知后,要是只读了一半,然后,不读了,应该后面不会再有EPOLLIN通知了吧,该如何读剩下的一半(client端一直连接着,没有断开)?

[ 本帖最后由 anders0913 于 2009-6-21 19:41 编辑 ]

论坛徽章:
0
10 [报告]
发表于 2009-06-21 20:20 |只看该作者

回复 #9 anders0913 的帖子

所以在ET模式下需要保证“只有当read()或write()返回EAGAIN(对于面向包/令牌的文件,比如数据包套接口、规范模式的终端)或是read()/write()读到/写出的数据长度小于请求的数据长度(对于面向流的文件,比如pipe、FIFO、流套接口)时才需要挂起等待下一个事件。”,否则只是自找麻烦呗。
另外ET模式当然最好是基于非阻塞文件描述符。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP