本博客采用创作共用版权协议, 要求署名、非商业用途和保持一致. 转载本博客文章必须也遵循 署名-非商业用途-保持一致 的创作共用协议.
想在Mac上造点小轮子, 然而epoll是在Linux平台独有的, 所以想到了用kqueue来替代. 记录一下自己的学习过程.
kqueue
是在UNIX上高效的IO复用技术, 类比于linux平台中的 epoll
.
kqueue使用的头文件和api可以通过 man kqueue
来看到
#include<sys/types.h> #include<sys/event.h> #include<sys/time.h>
kqueue()
系统调用会创建一个新的内核消息队列并返回描述符. # 创建失败返回-1, 否则返回描述符 intkqueue(void);
structkevent{ uintptr_t ident; /* identifier for this event, 该事件关联的文件描述符 */ int16_t filter; /* filter for event */ 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); // 使用范例, 将监听stdin描述符的可读事件初始化到ev数据结构 kevent ev; EV_SET(&ev, STDIN_FILENO, EVFILT_READ, EV_ADD, 0, 0, 0);
kevent
为核心函数, 初始时 kqueue
内核消息队列为空, 使用kevent进行事件填充, 在不设置超时参数时, 只有当收到某监听事件才会返回. 该函数返回接收到事件个数, 并将事件写入eventlist # changelist为要注册的事件列表, eventlist用于返回已经就绪的事件列表 intkevent(intkq,conststructkevent *changelist,intnchanges,structkevent *eventlist,intnevents,conststructtimespec *timeout);
#include<stdio.h>// fprintf #include<sys/event.h>// kqueue #include<netdb.h>// addrinfo #include<arpa/inet.h>// AF_INET #include<sys/socket.h>// socket #include<assert.h>// assert #include<string.h>// bzero #include<stdbool.h>// bool #include<unistd.h>// close constsize_t BUF_SIZE =1024; staticbools_stop =true; // 信号处理函数 staticvoidhandle_signal(intsig) { s_stop = false; } intlearn_kqueue(constchar* ip, int32_t port) { std::cout<<"ip: "<< ip <<" port: "<< port << std::endl; signal(SIGTERM, handle_signal); intsock = socket(PF_INET, SOCK_STREAM,0); assert(sock > 0); // 强制使用TIME_WAIT状态的socket地址 intreuse =1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); structsockaddr_in address; bzero(&address, sizeof(address)); address.sin_family = AF_INET; inet_pton(AF_INET, ip, &address.sin_addr); //主机序转网络序ip address.sin_port = htons(port); //主机序转网络序 intret = bind(sock, (structsockaddr*)&address,sizeof(address)); assert(ret != -1); ret = listen(sock, BACK_LOG); assert(ret != -1); //创建一个消息队列并返回kqueue描述符 intkq = kqueue(); assert(kq != -1); structkevent change_list[10];//想要监控的事件 structkevent event_list[10];//用于kevent返回 charbuf[BUF_SIZE]; // 监听sock的读事件 EV_SET(&change_list[0], sock, EVFILT_READ, EV_ADD | EV_ENABLE,0,0,0); // 监听stdin的读事件 EV_SET(&change_list[1], fileno(stdin), EVFILT_READ, EV_ADD | EV_ENABLE,0,0,0); intnevents; while(s_stop) { printf("new loop.../n"); // 等待监听事件的发生 nevents = kevent(kq, change_list, 2, event_list,2, NULL); if(nevents <0) { printf("kevent error./n");// 监听出错 } elseif(nevents >0) { printf("get events number: %d/n", nevents); for(inti =0; i < nevents; ++i) { printf("loop index: %d/n", i); structkevent event = event_list[i];//监听事件的event数据结构 intclientfd = (int) event.ident;// 监听描述符 // 表示该监听描述符出错 if(event.flags & EV_ERROR) { close(clientfd); printf("EV_ERROR: %s/n", strerror(event_list[i].data)); } // 表示sock有新的连接 if(clientfd == sock) { printf("new connection/n"); structsockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); intnew_fd = accept(sock, (structsockaddr *) &client_addr, &client_addr_len); charremote[INET_ADDRSTRLEN]; printf("connected with ip: %s, port: %d/n", inet_ntop(AF_INET, &client_addr.sin_addr, remote, INET_ADDRSTRLEN), ntohs(client_addr.sin_port)); } if(clientfd == fileno(stdin)) { memset(buf,0, BUF_SIZE); fgets(buf, BUF_SIZE, stdin); printf("data from stdin: %s/n", buf); } } } } close(sock); return0; }