网络服务器通常都使用epoll进行异步IO处理,而开发者通常使用mac,为了方便开发,我把自己的handy库移植到了mac平台上。移植过程中,网上居然没有搜到kqueue的使用例子,让我惊讶不已。为了让大家不用像我一样再次花费大力气搞定kqueue,我整理了一个简单清晰可运行的kqueue例子,供大家参考。
kqueue一共有几个函数:
1 //类似epoll_create 2 int kqueue(void); 3 //兼具epoll_ctl及epoll_wait功能 4 int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout); 5 //设定kevent参数的宏 6 EV_SET(&kev, ident, filter, flags, fflags, data, udata); 7 struct kevent { 8 uintptr_t ident; /* identifier for this event */ 9 int16_t filter; /* filter for event */ 10 uint16_t flags; /* general flags */ 11 uint32_t fflags; /* filter-specific flags */ 12 intptr_t data; /* filter-specific data */ 13 void *udata; /* opaque user data identifier */ 14 };
函数调用示例:
1 //创建kqueue 2 int epollfd = kqueue(); 3 //添加或者修改fd 4 struct kevent ev[2]; 5 int n = 0; 6 if (events & kReadEvent) { 7 EV_SET(&ev[n++], fd, EVFILT_READ, EV_ADD|EV_ENABLE, 0, 0, (void*)(intptr_t)fd); 8 } else if (modify){ 9 EV_SET(&ev[n++], fd, EVFILT_READ, EV_DELETE, 0, 0, (void*)(intptr_t)fd); 10 } 11 if (events & kWriteEvent) { 12 EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_ADD|EV_ENABLE, 0, 0, (void*)(intptr_t)fd); 13 } else if (modify){ 14 EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_DELETE, 0, 0, (void*)(intptr_t)fd); 15 } 16 printf("%s fd %d events read %d write %d\n", 17 modify ? "mod" : "add", fd, events & kReadEvent, events & kWriteEvent); 18 int r = kevent(efd, ev, n, NULL, 0, NULL); 19 //获取ready的fd 20 struct timespec timeout; 21 timeout.tv_sec = waitms / 1000; 22 timeout.tv_nsec = (waitms % 1000) * 1000 * 1000; 23 const int kMaxEvents = 20; 24 struct kevent activeEvs[kMaxEvents]; 25 int n = kevent(efd, NULL, 0, activeEvs, kMaxEvents, &timeout); 26 //处理IO事件 27 for (int i = 0; i < n; i ++) { 28 int fd = (int)(intptr_t)activeEvs[i].udata; 29 int events = activeEvs[i].filter; 30 if (events == EVFILT_READ) { 31 handleRead(efd, fd); 32 } else if (events == EVFILT_WRITE) { 33 handleWrite(efd, fd); 34 } 35 }
注意kevent与epoll最大的不同在于READ/WRITE事件是分开注册并且分开返回的,而Epoll则是一个fd一次返回读和写事件,用标志位来判断。
可以运行的代码如下:kqueue-example(handy对kqueue提供了封装版本)
1 #include <sys/socket.h> 2 #include <sys/event.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 #include <fcntl.h> 6 #include <unistd.h> 7 #include <stdio.h> 8 #include <errno.h> 9 #include <string.h> 10 #include <stdlib.h> 11 12 #define exit_if(r, ...) if(r) {printf(__VA_ARGS__); printf("error no: %d error msg %s\n", errno, strerror(errno)); exit(1);} 13 14 const int kReadEvent = 1; 15 const int kWriteEvent = 2; 16 17 void setNonBlock(int fd) { 18 int flags = fcntl(fd, F_GETFL, 0); 19 exit_if(flags<0, "fcntl failed"); 20 int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); 21 exit_if(r<0, "fcntl failed"); 22 } 23 24 void updateEvents(int efd, int fd, int events, bool modify) { 25 struct kevent ev[2]; 26 int n = 0; 27 if (events & kReadEvent) { 28 EV_SET(&ev[n++], fd, EVFILT_READ, EV_ADD|EV_ENABLE, 0, 0, (void*)(intptr_t)fd); 29 } else if (modify){ 30 EV_SET(&ev[n++], fd, EVFILT_READ, EV_DELETE, 0, 0, (void*)(intptr_t)fd); 31 } 32 if (events & kWriteEvent) { 33 EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_ADD|EV_ENABLE, 0, 0, (void*)(intptr_t)fd); 34 } else if (modify){ 35 EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_DELETE, 0, 0, (void*)(intptr_t)fd); 36 } 37 printf("%s fd %d events read %d write %d\n", 38 modify ? "mod" : "add", fd, events & kReadEvent, events & kWriteEvent); 39 int r = kevent(efd, ev, n, NULL, 0, NULL); 40 exit_if(r, "kevent failed "); 41 } 42 43 void handleAccept(int efd, int fd) { 44 struct sockaddr_in raddr; 45 socklen_t rsz = sizeof(raddr); 46 int cfd = accept(fd,(struct sockaddr *)&raddr,&rsz); 47 exit_if(cfd<0, "accept failed"); 48 sockaddr_in peer, local; 49 socklen_t alen = sizeof(peer); 50 int r = getpeername(cfd, (sockaddr*)&peer, &alen); 51 exit_if(r<0, "getpeername failed"); 52 printf("accept a connection from %s\n", inet_ntoa(raddr.sin_addr)); 53 setNonBlock(cfd); 54 updateEvents(efd, cfd, kReadEvent|kWriteEvent, false); 55 } 56 57 void handleRead(int efd, int fd) { 58 char buf[4096]; 59 int n = 0; 60 while ((n=::read(fd, buf, sizeof buf)) > 0) { 61 printf("read %d bytes\n", n); 62 int r = ::write(fd, buf, n); //写出读取的数据 63 //实际应用中,写出数据可能会返回EAGAIN,此时应当监听可写事件,当可写时再把数据写出 64 exit_if(r<=0, "write error"); 65 } 66 if (n<0 && (errno == EAGAIN || errno == EWOULDBLOCK)) 67 return; 68 exit_if(n<0, "read error"); //实际应用中,n<0应当检查各类错误,如EINTR 69 printf("fd %d closed\n", fd); 70 close(fd); 71 } 72 73 void handleWrite(int efd, int fd) { 74 //实际应用应当实现可写时写出数据,无数据可写才关闭可写事件 75 updateEvents(efd, fd, kReadEvent, true); 76 } 77 78 void loop_once(int efd, int lfd, int waitms) { 79 struct timespec timeout; 80 timeout.tv_sec = waitms / 1000; 81 timeout.tv_nsec = (waitms % 1000) * 1000 * 1000; 82 const int kMaxEvents = 20; 83 struct kevent activeEvs[kMaxEvents]; 84 int n = kevent(efd, NULL, 0, activeEvs, kMaxEvents, &timeout); 85 printf("epoll_wait return %d\n", n); 86 for (int i = 0; i < n; i ++) { 87 int fd = (int)(intptr_t)activeEvs[i].udata; 88 int events = activeEvs[i].filter; 89 if (events == EVFILT_READ) { 90 if (fd == lfd) { 91 handleAccept(efd, fd); 92 } else { 93 handleRead(efd, fd); 94 } 95 } else if (events == EVFILT_WRITE) { 96 handleWrite(efd, fd); 97 } else { 98 exit_if(1, "unknown event"); 99 } 100 } 101 } 102 103 int main() { 104 short port = 99; 105 int epollfd = kqueue(); 106 exit_if(epollfd < 0, "epoll_create failed"); 107 int listenfd = socket(AF_INET, SOCK_STREAM, 0); 108 exit_if(listenfd < 0, "socket failed"); 109 struct sockaddr_in addr; 110 memset(&addr, 0, sizeof addr); 111 addr.sin_family = AF_INET; 112 addr.sin_port = htons(port); 113 addr.sin_addr.s_addr = INADDR_ANY; 114 int r = ::bind(listenfd,(struct sockaddr *)&addr, sizeof(struct sockaddr)); 115 exit_if(r, "bind to 0.0.0.0:%d failed %d %s", port, errno, strerror(errno)); 116 r = listen(listenfd, 20); 117 exit_if(r, "listen failed %d %s", errno, strerror(errno)); 118 printf("fd %d listening at %d\n", listenfd, port); 119 setNonBlock(listenfd); 120 updateEvents(epollfd, listenfd, kReadEvent, false); 121 for (;;) { //实际应用应当注册信号处理函数,退出时清理资源 122 loop_once(epollfd, listenfd, 10000); 123 } 124 return 0; 125 }