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

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

#include 
#include 
#include 
#include 
#include 
#include
#include
#include
#include
#include
#include

#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
#include
#include
#include
#include
#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
epoll 转kqueue的用法介绍和实例 实现跨平台Macos_第1张图片

实验依据


你可能感兴趣的:(Linux服务器编程,c++,linux)