资讯详情

epoll 转kqueue的用法介绍和实例 实现跨平台Macos

网上关于kqueue的博客很少 让我补充一个例子echo 的例子

#include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include<stdio.h> #include<arpa/inet.h> #include<sys/event.h> #include<sys/types.h> #include<unistd.h> #include<ctype.h>  #define MAXLEN 1024 #define SERV_PORT 8000 #define MAX_OPEN_FD 1024   // 工具函数错误退出 int quit(const char *msg){ 
            perror(msg);     exit(1); }  void setNonBlock(int fd) { 
            int flags = fcntl(fd, F_GETFL, 0);     int r = fcntl(fd, F_SETFL, flags 
      
       | O_NONBLOCK
       )
       ; 
       } 
       const 
       static 
       int FD_NUM 
       = 
       2
       ; 
       // 两个文件描述符,分别为标准输入与输出 
       const 
       static 
       int BUFFER_SIZE 
       = 
       1024
       ; 
       // 缓冲区大小 
       // 完全以IO复用的方式读入标准输入流数据,输出到标准输出流中 
       int 
       main
       (
       )
       { 
         
       int listenfd
       ,connfd
       ,efd
       ,ret
       ; 
       char buf
       [MAXLEN
       ]
       ; 
       struct 
       sockaddr_in cliaddr
       ,servaddr
       ; socklen_t clilen 
       = 
       sizeof
       (cliaddr
       )
       ; 
       struct 
       kevent tep
       [
       2
       ]
       ,ep
       [MAX_OPEN_FD
       ]
       ; listenfd 
       = 
       socket
       (AF_INET
       ,SOCK_STREAM
       ,
       0
       )
       ; servaddr
       .sin_family 
       = AF_INET
       ; servaddr
       .sin_addr
       .s_addr 
       = 
       htonl
       (INADDR_ANY
       )
       ; servaddr
       .sin_port 
       = 
       htons
       (SERV_PORT
       )
       ; 
       bind
       (listenfd
       ,
       (
       struct 
       sockaddr
       *
       )
       &servaddr
       ,
       sizeof
       (servaddr
       )
       )
       ; 
       listen
       (listenfd
       ,
       20
       )
       ; 
       struct 
       kevent changes
       [FD_NUM
       ]
       ; 
       struct 
       kevent events
       [FD_NUM
       ]
       ; 
       // 创建一个kqueue 
       int kq
       ; 
       // if( (kq = kqueue()) == -1 ) quit("kqueue()"); kq 
       = 
       kqueue
       (
       )
       ; 
       //kqueue(); 对应epoll_create 
       // 设置为非阻塞 
       setNonBlock
       (listenfd
       )
       ; 
       // 注册监听事件 
       int k 
       = 
       0
       ; 
       // EV_SET代替epoll 
       //tep.events = EPOLLIN; 
       //tep.data.fd = connfd; 
       EV_SET
       (
       &changes
       [k
       ++
       ]
       , listenfd
       , EVFILT_READ
       , EV_ADD 
       | EV_ENABLE
       , 
       0
       , 
       0
       , 
       (
       void 
       *
       ) 
       (intptr_t
       )listenfd
       )
       ; 
       EV_SET
       (
       &changes
       [k
       ++
       ]
       , listenfd
       , EVFILT_WRITE
       , EV_ADD 
       | EV_ENABLE
       , 
       0
       , 
       0
       , 
       (
       void 
       *
       ) 
       (intptr_t
       )listenfd
       )
       ; 
       kevent
       (kq
       , changes
       , FD_NUM
       , 
       NULL
       , 
       0
       , 
       NULL
       )
       ;
       //kevent 可以同时代替epoll_ctl和epoll_wait 生成的实例也就是调用epoll_ctl的时候只需要第2第3 参数 而代替epoll_wait的时候需要第4第5参数 
       int nev
       , nread
       , nwrote 
       = 
       0
       ; 
       // 发生事件的数量, 已读字节数, 已写字节数 
       char buffer
       [BUFFER_SIZE
       ]
       ; 
       int lastActive_
       ; 
       const 
       int kMaxEvents 
       = 
       2000
       ; 
       struct 
       kevent activeEvs_
       [kMaxEvents
       ]
       ; 
       while
       (
       1
       )
       { 
         
       //lastActive_ 活跃的事件数量 lastActive_ 
       = 
       kevent
       (kq
       , 
       NULL
       , 
       0
       , activeEvs_
       , kMaxEvents
       , 
       NULL
       )
       ; 
       // 已经就绪的文件描述符数量 epoll_wait 
       // if( nev <= 0 ) quit("kevent()"); 
       int i
       ; 
       for
       (i
       =
       0
       ; i
       <lastActive_
       ; i
       ++
       )
       { 
         
       struct 
       kevent event 
       = activeEvs_
       [i
       ]
       ; 
       if
       ( event
       .flags 
       & EV_ERROR 
       ) 
       quit
       (
       "Event error"
       )
       ; 
       int ev_fd 
       = 
       (
       int
       )
       (intptr_t
       )activeEvs_
       [i
       ]
       .udata
       ; 
       if 
       (ev_fd 
       == listenfd 
       ) 
       { 
         connfd 
       = 
       accept
       (listenfd
       ,
       (
       struct 
       sockaddr
       *
       )
       &cliaddr
       ,
       &clilen
       )
       ; 
       printf
       (
       "connfd=%d"
       ,connfd
       )
       ; 
       setNonBlock
       (connfd
       )
       ; 
       EV_SET
       (
       &changes
       [
       0
       ]
       , connfd
       , EVFILT_READ
       , EV_ADD 
       | EV_ENABLE
       , 
       0
       , 
       0
       , 
       (
       void 
       *
       ) 
       (intptr_t
       )connfd
       )
       ; 
       EV_SET
       (
       &changes
       [
       1
       ]
       , connfd
       , EVFILT_WRITE
       , EV_ADD 
       | EV_ENABLE
       , 
       0
       , 
       0
       , 
       (
       void 
       *
       ) 
       (intptr_t
       )connfd
       )
       ; 
       kevent
       (kq
       , changes
       , 
       2
       , 
       NULL
       , 
       0
       , 
       NULL
       )
       ; 
       } 
       // 否则,读取数据 
       else 
       { 
         
       int bytes 
       = 
       read
       (ev_fd
       ,buf
       ,MAXLEN
       )
       ; 
       // 客户端关闭连接 
       if 
       (bytes 
       == 
       0
       )
       { 
         
       close
       (ev_fd
       )
       ; 
       printf
       (
       "client[%d] closed\n"
       , i
       )
       ; 
       } 
       else 
       { 
         
       for 
       (
       int j 
       = 
       0
       ; j 
       < bytes
       ; 
       ++j
       ) 
       { 
         buf
       [j
       ] 
       = 
       toupper
       (buf
       [j
       ]
       )
       ; 
       //把小写字母装换为大写 
       } 
       // 向客户端发送数据 
       write
       (ev_fd
       ,buf
       ,bytes
       )
       ; 
       } 
       } 
       } 
       } 
       return 
       0
       ; 
       } 
      

struct kevent 结构体内容如下:

struct kevent { 
       
    uintptr_t       ident;          /* identifier for this event,比如该事件关联的文件描述符 */
    int16_t         filter;         /* filter for event,可以指定监听类型,如EVFILT_READ,EVFILT_WRITE,EVFILT_TIMER等 */
    uint16_t        flags;          /* general flags ,可以指定事件操作类型,比如EV_ADD,EV_ENABLE, EV_DELETE等 */
    uint32_t        fflags;         /* filter-specific flags */
    intptr_t        data;           /* filter-specific data */
    void            *udata;         /* opaque user data identifier,可以携带的任意数据 */
};

EV_SET 是用于初始化kevent结构的便利宏:

EV_SET(&kev, ident, filter, flags, fflags, data, udata);

kevent 是IO复用的函数,其签名为:

int kevent(int kq, 
    const struct kevent *changelist, // 监视列表
    int nchanges, // 长度
    struct kevent *eventlist, // kevent函数用于返回已经就绪的事件列表
    int nevents, // 长度
    const struct timespec *timeout); // 超时限制

附上原epoll的实例方便对比

#include<stdio.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<ctype.h>
#define MAXLEN 1024
#define SERV_PORT 8000
#define MAX_OPEN_FD 1024

int main(int argc,char *argv[])
{ 
       
    int  listenfd,connfd,efd,ret;
    char buf[MAXLEN];
    struct sockaddr_in cliaddr,servaddr;
    socklen_t clilen = sizeof(cliaddr);
    struct epoll_event tep,ep[MAX_OPEN_FD];

    listenfd = socket(AF_INET,SOCK_STREAM,0);

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    listen(listenfd,20);
    // 创建一个epoll fd
    efd = epoll_create(MAX_OPEN_FD);
    tep.events = EPOLLIN;tep.data.fd = listenfd;
    // 把监听socket 先添加到efd中
    ret = epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&tep);
    // 循环等待
    for (;;)
    { 
       
        // 返回已就绪的epoll_event,-1表示阻塞,没有就绪的epoll_event,将一直等待
        size_t nready = epoll_wait(efd,ep,MAX_OPEN_FD,-1);
        for (int i = 0; i < nready; ++i)
        { 
       
            // 如果是新的连接,需要把新的socket添加到efd中
            if (ep[i].data.fd == listenfd )
            { 
       
                connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
                tep.events = EPOLLIN;
                tep.data.fd = connfd;
                ret = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&tep);
            }
                // 否则,读取数据
            else
            { 
       
                connfd = ep[i].data.fd;
                int bytes = read(connfd,buf,MAXLEN);
                // 客户端关闭连接
                if (bytes == 0){ 
       
                    ret =epoll_ctl(efd,EPOLL_CTL_DEL,connfd,NULL);
                    close(connfd);
                    printf("client[%d] closed\n", i);
                }
                else
                { 
       
                    for (int j = 0; j < bytes; ++j)
                    { 
       
                        buf[j] = toupper(buf[j]);
                    }
                    // 向客户端发送数据
                    write(connfd,buf,bytes);
                }
            }
        }
    }
    return 0;
}

redis源码研究 里面 EV_SET的最后一个参数为什么是NULL 上面实例如果置为NULL会导致数据接收不到

static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 
       
    aeApiState *state = eventLoop->apidata;
    struct kevent ke;

    if (mask & AE_READABLE) { 
       
        EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
        if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1;
    }
    if (mask & AE_WRITABLE) { 
       
        EV_SET(&ke, fd, EVFILT_WRITE, EV_ADD, 0, 0, NULL);
        if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1;
    }
    return 0;
}

static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { 
       
    aeApiState *state = eventLoop->apidata;
    struct kevent ke;

    if (mask & AE_READABLE) { 
       
        EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
        kevent(state->kqfd, &ke, 1, NULL, 0, NULL);
    }
    if (mask & AE_WRITABLE) { 
       
        EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
        kevent(state->kqfd, &ke, 1, NULL, 0, NULL);
    }
}

实际上就是个普通的赋值类似个构造函数 也就是我后面用到了udata 所以当然不赋值没效果喽

#define EV_SET(kevp, a, b, c, d, e, f) do { 
          \ struct kevent *__kevp__ = (kevp); \ __kevp__->ident = (a); \ __kevp__->filter = (b); \ __kevp__->flags = (c); \ __kevp__->fflags = (d); \ __kevp__->data = (e); \ __kevp__->udata = (f); \ } while(0)

根据伯克利大学的研究,kqueue的性能优于epoll,主要是因为epoll不支持在一个系统调用中进行多个兴趣更新,而kqueue可以使用kevent()来实现这一点。在

还有一篇技术论文对二者的区别和性能进行了比较。在

http://www.eecs.berkeley.edu/~sangjin/2012/12/21/epoll-vs-kqueue.html 在这里插入图片描述

实验依据

标签: ke全铜连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

 锐单商城 - 一站式电子元器件采购平台  

 深圳锐单电子有限公司