libevent库源码学习-kqueue( freebsd)
kqueue 提供 kqueue()、kevent() 两个系统调用和 struct kevent 结构。
int kqueue(void) |
生成一个内核事件队列,返回该队列的文件描述索。其它 API 通过该描述符操作这个 kqueue。生成的多个 kqueue 的结构类似图 1 所示。
int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout); |
kevent 提供向内核注册 / 反注册事件和返回就绪事件或错误事件: kq: kqueue 的文件描述符。 changelist: 要注册 / 反注册的事件数组; nchanges: changelist 的元素个数。 eventlist: 满足条件的通知事件数组; nevents: eventlist 的元素个数。 timeout: 等待事件到来时的超时时间,0,立刻返回;NULL,一直等待;有一个具体值,等待 timespec 时间值。 返回值:可用事件的个数。
通过 kevent() 提供三个主要的行为功能。在下面小节中将会用到这两个主要功能。
- 注册 / 反注册
注意 kevent() 中的 eventlist 这个输入参数,当将其设为 0,且传入合法的 changelist 和 nchanges,就会将 changelist 中的事件注册到 kqueue 中。
当关闭某文件描述符时,与之关联的事件会被自动地从 kqueue 移除。
-
允许 / 禁止过滤器事件
通过 flags EV_ENABLE 和 EV_DISABLE 使过滤器事件有效或无效。这个功能在利用 EVFILT_WRITE 发送数据时非常有用。
- 等待事件通知
将 changelist 设置成 0,当然要传入其它合法的参数,当 kevent 非错误和超时返回时,在 eventlist 和 neventlist 中就保存可用事件集合。
struct kevent { uintptr_t ident; /* 事件 ID */ short filter; /* 事件过滤器 */ u_short flags; /* 行为标识 */ u_int fflags; /* 过滤器标识值 */ intptr_t data; /* 过滤器数据 */ void *udata; /* 应用透传数据 */ }; 在一个 kqueue 中,{ident, filter} 确定一个唯一的事件。 |
-
ident
事件的 id,实际应用中,一般设置为文件描述符。
filter
可以将 kqueue filter 看作事件。内核检测 ident 上注册的 filter 的状态,状态发生了变化,就通知应用程序。kqueue 定义了较多的 filter,本文只介绍 Socket 读写相关的 filter。
EVFILT_READ
TCP 监听 socket,如果在完成的连接队列 ( 已收三次握手最后一个 ACK) 中有数据,此事件将被通知。收到该通知的应用一般调用 accept(),且可通过 data获得完成队列的节点个数。 流或数据报 socket,当协议栈的 socket 层接收缓冲区有数据时,该事件会被通知,并且 data 被设置成可读数据的字节数。
EVFILT_WRIT
当 socket 层的写入缓冲区可写入时,该事件将被通知;data 指示目前缓冲区有多少字节空闲空间。
flags
EV_ADD
指示加入事件到 kqueue。
EV_DELETE
指示将传入的事件从 kqueue 中移除。
EV_ENABLE
过滤器事件可用,注册一个事件时,默认是可用的。
EV_DISABLE
过滤器事件不可用,当内部描述可读或可写时,将不通知应用程序。第 5 小节有这个 flag 的用法介绍。
EV_ERROR
一个输出参数,当 changelist 中对应的描述符处理出错时,将输出这个 flag。应用程序要判断这个 flag,否则可能出现 kevent 不断地提示某个描述符出错,却没将这个描述符从 kq 中清除。处理 EV_ERROR 类似下面的代码: if (events[i].flags & EV_ERROR) close(events[i].ident); fflags 过滤器相关的一个输入输出类型标识,有时候和 data 结合使用。
data
过滤器相关的数据值,请看 EVFILT_READ 和 EVFILT_WRITE 描述。
udata
应用自定义数据,注册的时候传给 kernel,kernel 不会改变此数据,当有事件通知时,此数据会跟着返回给应用。
EV_SETEV_SET(&kev, ident, filter, flags, fflags, data, udata);
struct kevent 的初始化的辅助操作。
一个服务器示例
例子实现了一个只有较简单通信功能的但有性能保证的服务器。在下面各个清单中只写出关键性的代码,错误处理的代码未写出,完整的代码请参考附带的源码:kqueue.cpp。
-
注册事件到 kqueue
清单 1. 注册事件
73 bool Register(int kq, int fd) 74 { 75 struct kevent changes[1]; 76 EV_SET(&changes[0], fd, EVFILT_READ, EV_ADD, 0, 0, NULL); 77 78 int ret = kevent(kq, changes, 1, NULL, 0, NULL); 81 82 return true; 83 } Register 将 fd 注册到 kq 中。注册的方法是通过 kevent() 将 eventlist 和 neventlist 置成 NULL 和 0 来达到的。
-
创建监听 socket 和 kqueue,等待内核事件通知
清单 2. 创建监听
27 int main(int argc, char* argv[]) 28 { 29 listener_ = CreateListener(); 32 33 int kq = kqueue(); 34 if (!Register(kq, listener_)) 39 40 WaitEvent(kq); 41 42 return 0; 43 } 85 void WaitEvent(int kq) 86 { 87 struct kevent events[MAX_EVENT_COUNT]; 88 while (true) 89 { 90 int ret = kevent(kq, NULL, 0, events, MAX_EVENT_COUNT, NULL); 96 97 HandleEvent(kq, events, ret); 98 } 99 }
29~40,创建监听 socket,将监听 socket 注册到 kq,然后等待事件。 90,这一行就是 kevent 事件等待方法,将 changelist 和 nchangelist 分别置成 NULL 和 0,并且传一个足够大的 eventlist 空间给内核。当有事件过来时,kevent 返回,这时调用 HandleEvent 处理可用事件。
-
struct kevent data 字段在 accept 和 recv 时的用法
清单 3. 接收数据
101 void HandleEvent(int kq, struct kevent* events, int nevents) 102 { 103 for (int i = 0; i < nevents; i++) 104 { 105 int sock = events[i].ident; 106 int data = events[i].data; 107 108 if (sock == listener_) 109 Accept(kq, data); 110 else 111 Receive(sock, data); 112 } 113 } 114 115 void Accept(int kq, int connSize) 116 { 117 for (int i = 0; i < connSize; i++) 118 { 119 int client = accept(listener_, NULL, NULL); 125 126 if (!Register(kq, client)) 131 } 132 } 133 134 void Receive(int sock, int availBytes) 135 { 136 int bytes = recv(sock, buf_, availBytes, 0); 145 Enqueue(buf_, bytes); 146 }
108~111,根据 events.ident 的类型来调用 Accept() 或 Receive()。这里要注意的是 events[i].data。
117~126,对于监听 socket,data 表示连接完成队列中的元素 ( 已经收到三次握手最后一个 ACK) 个数。119 行演示了这种用法,accept data 次。126 行将 accept 成功的 socket 注册到 kq。
136~145,对于流 socket,data 表示协议栈 socket 层的接收缓冲区可读数据的字节数。recv 时显示地指定接收 availBytes 字节 ( 就是 data)。这个功能点将对 recv 和 send 的性能提升有积极的作用,第 4 小节将这方面的讨论。145 行表示将收到的数据入缓冲队列。
转自:http://www.ibm.com/developerworks/cn/aix/library/1105_huangrg_kqueue/#icomments -
libevent库源码学习-evport(Event ports)(Solaris 10)
Solaris 10 的新增加的特性。
一.API
1. port_create()
原型:int port_create(void);
port_create() 创建一个 Event ports 队列,返回一个文件描述符作为该
Event port 的代表。
相似:kqueue(),epoll_create()
2. port_associate()
原型:int port_associate(int port, int source, uintptr_t object,
int events, void *user);
port_associate() 将某一个对象的特定 event 与 Event port 相关联。当
source 为 PORT_SOURCE_FD 时,object 就是文件描述符。events 可以参考
poll(2) 的。user 是一个用户自定义的指针,与该 object 相关的。在
kqueue(2) 和 epoll(4) 也提供了类似的用户自定义指针。在前面的 echo
server 例子中,传入了一个函数指针给 user,这样在 main() 的事件处理
主循环中,代码就可以写得非常简洁。
需要注意的是,当用 port_get() 取得某个 object 的 event 之后,这个
object 与 port 也就不再相关联了。如果想继续取得这个 object 的 event,
必须再次调用 port_associate() 将 object 与 port 关联。这种设计显然
是为多线程程序而做的,当某一个线程取得一个 event 之后,object 就从
port 的对列中删掉了,这样可以保证这个线程完完整整地处理完这个 event,
不用担心别的线程也会取得这个 event。
相似:kevent(),epoll_ctl()
3. port_get()
原型:int port_get(int port, port_event_t *pe, const timespec_t
*timeout);
port_get() 每次从 port 中取回一个 event。如果 timeout 参数为 NULL,
port_get() 会一直等待,直到某一个 event 到来。pe 用于存储返回的
event。
相似:kevent(),epoll_wait()
4. port_getn()
原型:int port_getn(int port, port_event_t list[], uint_t max,
uint_t *nget, const timespec_t *timeout);
port_getn() 与 port_get() 都是用于从 port 中取回 event,不同的是
port_getn() 可以一次取回多个。list 数组用于存储返回的多个 events,
max 为 list 数组的元素个数。timeout 与 port_get() 的一致。
需要特别注意的是 nget 参数,这是一个 value-result 参数,也就是传入
n,告诉内核要取得 n 个 event,当 port_getn() 返回时,getn 的值表示
内核实际取得了多少个。当 timeout 为 NULL 时,port_getn() 会一直等待,
直到确实取得了 n 个 event 之后才会返回,这一点是与 kevent() 和
epoll_wait() 很不相同的地方。如果 timeout 不为 NULL,则只要超时就返
回,不管是不是已经取到了 n 个 event。(注:这里忽略了其他可能引起
port_getn() 返回的因素) 这也是前面的 echo server 代码中要设置一个
50 毫秒超时的原因。大家可以试试把 NULL 传给 timeout 的效果。
相似:kevent(),epoll_wait()
二.例子
- str_srv.c:
- /* 用 Event ports 实现的 UNPv1 ECHO Server 例子程序。
- *
- * 编译命令:
- * gcc -o str_srv str_srv.c -lsocket
- *
- * flyriver 2004.11.02
- */
- #include <stdio.h>
- #include <netinet/in.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <sys/poll.h>
- #include <errno.h>
- #include <stdlib.h>
- #include <limits.h>
- #include <fcntl.h>
- #include <port.h> /* for event ports */
- #define SERV_PORT 9877
- #define MAXLINE 80
- #ifndef OPEN_MAX
- #define OPEN_MAX 256
- #endif
- typedef void (*port_func_t)(port_event_t *);
- int portfd;
- uint_t maxi;
- static void echo(port_event_t *ev)
- {
- ssize_t n;
- int sockfd;
- char buf[MAXLINE];
- sockfd = ev->portev_object;
- if (ev->portev_events & POLLIN)
- {
- if ((n = read(sockfd, buf, MAXLINE)) < 0)
- {
- perror("read");
- close(sockfd);
- maxi--;
- }
- else if (n == 0) /* connection closed by client */
- {
- close(sockfd);
- maxi--;
- }
- else
- {
- write(sockfd, buf, n);
- /* 重新关联 client fd 到 event ports */
- port_associate(portfd, PORT_SOURCE_FD, sockfd, POLLIN, echo);
- }
- }
- }
- static void new_conn(port_event_t *ev)
- {
- int connfd;
- socklen_t clilen;
- struct sockaddr_in cliaddr;
- int listenfd;
- listenfd = ev->portev_object;
- clilen = sizeof(cliaddr);
- connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
- /* 关联 client fd 到 event ports */
- port_associate(portfd, PORT_SOURCE_FD, connfd, POLLIN, echo);
- maxi++;
- /* 重新关联 listenfd 到 event ports */
- port_associate(portfd, PORT_SOURCE_FD, listenfd, POLLIN, new_conn);
- }
- int main()
- {
- int listenfd;
- struct sockaddr_in servaddr;
- int optval;
- /* variables for event ports version */
- int i;
- uint_t nready;
- port_event_t client[OPEN_MAX];
- timespec_t timeout;
- port_func_t port_func;
- bzero(&servaddr, sizeof(servaddr));
- 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);
- setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, 4);
- bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
- listen(listenfd, 5);
- /* 创建 event ports */
- portfd = port_create();
- /* 关联 listenfd 到 event ports */
- port_associate(portfd, PORT_SOURCE_FD, listenfd, POLLIN, new_conn);
- maxi++;
- timeout.tv_sec = 0;
- timeout.tv_nsec = 50000000; /* 50 毫秒 */
- for (;;)
- {
- /* get events here*/
- nready = maxi;
- if (port_getn(portfd, client, OPEN_MAX, &nready, &timeout) < 0
- && errno != ETIME)
- {
- if (errno == EINTR)
- continue;
- else
- {
- perror("port_getn");
- return -1;
- }
- }
- for (i = 0; i < nready; i++)
- {
- if (client[i].portev_source == PORT_SOURCE_FD)
- {
- port_func = (port_func_t)client[i].portev_user;
- port_func(&client[i]);
- }
- }
- }
- close(portfd);
- return 0;
- }
str_srv.c:
/* 用 Event ports 实现的 UNPv1 ECHO Server 例子程序。
*
* 编译命令:
* gcc -o str_srv str_srv.c -lsocket
*
* flyriver 2004.11.02
*/
#include <stdio.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/poll.h>
#include <errno.h>
#include <stdlib.h>
#include <limits.h>
#include <fcntl.h>
#include <port.h> /* for event ports */
#define SERV_PORT 9877
#define MAXLINE 80
#ifndef OPEN_MAX
#define OPEN_MAX 256
#endif
typedef void (*port_func_t)(port_event_t *);
int portfd;
uint_t maxi;
static void echo(port_event_t *ev)
{
ssize_t n;
int sockfd;
char buf[MAXLINE];
sockfd = ev->portev_object;
if (ev->portev_events & POLLIN)
{
if ((n = read(sockfd, buf, MAXLINE)) < 0)
{
perror("read");
close(sockfd);
maxi--;
}
else if (n == 0) /* connection closed by client */
{
close(sockfd);
maxi--;
}
else
{
write(sockfd, buf, n);
/* 重新关联 client fd 到 event ports */
port_associate(portfd, PORT_SOURCE_FD, sockfd, POLLIN, echo);
}
}
}
static void new_conn(port_event_t *ev)
{
int connfd;
socklen_t clilen;
struct sockaddr_in cliaddr;
int listenfd;
listenfd = ev->portev_object;
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
/* 关联 client fd 到 event ports */
port_associate(portfd, PORT_SOURCE_FD, connfd, POLLIN, echo);
maxi++;
/* 重新关联 listenfd 到 event ports */
port_associate(portfd, PORT_SOURCE_FD, listenfd, POLLIN, new_conn);
}
int main()
{
int listenfd;
struct sockaddr_in servaddr;
int optval;
/* variables for event ports version */
int i;
uint_t nready;
port_event_t client[OPEN_MAX];
timespec_t timeout;
port_func_t port_func;
bzero(&servaddr, sizeof(servaddr));
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);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, 4);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 5);
/* 创建 event ports */
portfd = port_create();
/* 关联 listenfd 到 event ports */
port_associate(portfd, PORT_SOURCE_FD, listenfd, POLLIN, new_conn);
maxi++;
timeout.tv_sec = 0;
timeout.tv_nsec = 50000000; /* 50 毫秒 */
for (;;)
{
/* get events here*/
nready = maxi;
if (port_getn(portfd, client, OPEN_MAX, &nready, &timeout) < 0
&& errno != ETIME)
{
if (errno == EINTR)
continue;
else
{
perror("port_getn");
return -1;
}
}
for (i = 0; i < nready; i++)
{
if (client[i].portev_source == PORT_SOURCE_FD)
{
port_func = (port_func_t)client[i].portev_user;
port_func(&client[i]);
}
}
}
close(portfd);
return 0;
}
参考:http://www.smth.edu.cn/bbsgcon.php?board=OS&file=T/G.1099404898.p0&num=526
http://bbs.tsinghua.edu.cn/bbsgcon.php?board=OS&file=j/G.1099375601.80&num=525