网上关于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
]
;
// 创建一个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<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 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 
实验依据