網上關于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];// 創建一個kqueueint 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 1024int 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 fdefd = 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

實驗依據