本博客采用创作共用版权协议, 要求署名、非商业用途和保持一致. 转载本博客文章必须也遵循 署名-非商业用途-保持一致 的创作共用协议.
想在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;
}