Kqueue 同时返回 EVFILT_READ 和 EVFILT_WRITE 但我安装了单独的 (ident,filt) 对

Posted

技术标签:

【中文标题】Kqueue 同时返回 EVFILT_READ 和 EVFILT_WRITE 但我安装了单独的 (ident,filt) 对【英文标题】:Kqueue returning both EVFILT_READ and EVFILT_WRITE but I installed separate (ident,filt) pairs 【发布时间】:2012-08-28 18:18:46 【问题描述】:

我遇到了一个问题,我不确定是 kqueue 的预期行为还是我做错了什么。

我需要使用 kqueue 为单个套接字文件描述符安装单独的事件。单独的事件是 (sockfd,EVFILT_READ) 和 (sockfd,EVFILT_WRITE) 的事件之一。然而,当我稍后从 kqueue 中提取事件时,我从 kqueue 中得到两个事件,但两个事件都包含 (event.filter & EVFILT_READ) 和 (event.filter & EVFILT_WRITE)。

我需要将它们分开,否则我不知道哪个事件实际上已准备好、读取或写入?

这是我为说明问题而整理的一些示例代码。请记住,这只是测试代码,以弄清楚为什么我在每个事件中都获得了两个过滤器标志。

int main(int argc, char ** argv) 
    //listening_sockfd = setup listening socket

    //setup kqueue and vars
    int kq = kqueue();
    int res = 0;
    int bufres = 0;
    struct timespec ts = 0;
    struct timespec * tsp = &ts;
    struct kevent ke2[2];

    //install read for listening socket
    struct kevent ke1;
    bzero(&ke1,sizeof(ke1));
    ke1.ident = listening_sockfd;
    ke1.flags = EV_ADD;
    ke1.filter = EVFILT_READ;
    res = kevent(kq,&ke1,1,NULL,0,tsp);

    while(1) 
        new_client:

        //wait for a client to connect
        res = kevent(kq,NULL,0,&ke1,1,NULL);
        if(res < 0) printf("%s\n", strerror(errno));

        if(res > 0) 
            //accept the client
            int clientfd = accept(skinfo.sockfd,NULL,NULL);

            //install read events for client
            bzero(&ke1,sizeof(ke1));
            ke1.ident = clientfd;
            ke1.flags = EV_ADD;
            ke1.filter = EVFILT_READ;
            res = kevent(kq,&ke1,1,NULL,0,tsp);
            if(res < 0) printf("%s\n",strerror(errno));

            while(1) 

                //wait for readable content from the client
                bzero(&ke1,sizeof(ke1));
                res = kevent(kq,NULL,0,&ke1,1,NULL);
                if(res < 0) printf("%s\n",strerror(errno));

                if(res > 0) 

                    //check for client disconnect
                    if(ke1.flags & EV_EOF) 
                        close(clientfd);
                        goto new_client;
                    

                    //now install write events for client
                    bzero(&ke1,sizeof(ke1));
                    ke1.ident = clientfd;
                    ke1.flags = EV_ADD;
                    ke1.filter = EVFILT_WRITE;
                    res = kevent(kq,&ke1,1,NULL,0,tsp);
                    if(res < 0) printf("%s\n",strerror(errno));

                    //now wait for it to be writable - this will return
                    //immediately because the socket is writable.
                    res = kevent(kq,NULL,0,ke2,2,NULL);
                    if(res < 0) printf("%s\n",strerror(errno));

                    if(res >= 0) 
                        char buf[128];
                        printf("res: %i\n",res);

                        //we have two events from kqueue because I installed
                        //two (ident,filter) pairs.
                        int i = 0;
                        for(i; i<2; i++) 
                            printf("ident: %i\n",ke2[i].ident);
                            printf("filter[%i] %lu\n",i,ke2[i].filter);
                            printf("data: %lu\n",ke2[i].data);

                            //QUESTION: Why does EVFILT_READ && EVFILT_WRITE come
                            //back in the same event when I installed two seperate
                            //(ident,filter) pairs?
                            if(ke2[i].filter & EVFILT_READ) printf("EVFILT_READ\n");
                            if(ke2[i].filter & EVFILT_WRITE) printf("EVFILT_WRITE\n");

                            if(ke2[i].filter & EVFILT_READ) 
                                printf("readable!\n");
                                bufres = read(clientfd,buf,128);
                            

                            if(ke2[i].filter & EVFILT_WRITE) 
                                printf("writable\n");

                                //shut off write events to stop infinite loop
                                //because the socket is writable
                                bzero(&ke1,sizeof(ke1));
                                ke1.ident = clientfd;
                                ke1.flags = EV_DELETE;
                                ke1.filter = EVFILT_WRITE;
                                res = kevent(kq,&ke1,1,NULL,0,tsp);

                                write(clientfd,buf,bufres);
                            
                        
                    
                
            
        
    

我无法弄清楚这一点。为什么我安装单独的事件时,每个 kqueue 事件都包含 EVFILT_READ 和 EVFILT_WRITE?

这给我带来的真正问题是,因为这些事件总是报告 EVFILT_READ,它具有代码的副作用,总是认为有可用的读取,但实际上没有,所以调用 read() 将是不成功并产生其他后果。请注意,此代码并未显示这些后果,这只是我编写的问题的一个示例,因此我弄清楚了这一点并继续使用我的真实代码。

有什么想法吗?

PS 下面是 printf 的输出:

res: 2
ident: 5
filter[0] 4294967295
data: 5
EVFILT_READ
EVFILT_WRITE
readable!
writable
ident: 5
filter[1] 4294967294
data: 146988
EVFILT_READ
EVFILT_WRITE
readable!
writable

【问题讨论】:

【参考方案1】:

啊!我想我想通了。 kqueue 文档没有说 event.filter 不包含位标志。所以我需要用 event.filter == EVFILT_READ, event.filter == EVFILT_WRITE 检查读/写。

【讨论】:

是的,遇到了完全相同的问题sigh 同一设计问题报告的另一个受害者。【参考方案2】:

如您所见,它们不是要测试的部分。

查阅OS X上的header,我们发现

#define EVFILT_READ             (-1)
#define EVFILT_WRITE            (-2)

由于-1 全部为 1 位,x &amp; EVFILT_READ 将适用于任何非零 xx &amp; EVFILT_WRITE 将适用于 x 不等于 0 或 1。

【讨论】:

以上是关于Kqueue 同时返回 EVFILT_READ 和 EVFILT_WRITE 但我安装了单独的 (ident,filt) 对的主要内容,如果未能解决你的问题,请参考以下文章

(dpdk f-stack)-Nginx使用kqueue

(dpdk f-stack)-Nginx使用kqueue

(dpdk f-stack)-Nginx使用kqueue

常规文件上的 Kqueue

使用 kqueue() 监视目录更改的最佳方法是啥?

#yyds干货盘点#高级IO模型之kqueue和epoll