libevent库源码学习-kqueue( freebsd) ,evport(Event ports)(Solaris 10)
来源:互联网 发布:淘宝预定手办流程 编辑:程序博客网 时间:2024/04/29 16:04
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#endiftypedef 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
- libevent库源码学习-kqueue( freebsd) ,evport(Event ports)(Solaris 10)
- libevent库源码学习-evport(Event ports)(Solaris 10)
- libevent库源码学习-kqueue( freebsd)
- libevent源码学习(一)event事件
- libevent事件(二)---event源码
- libevent源码学习研究(libevent-0.1)
- libevent库源码学习-epoll( linux)
- libevent源码学习-----event操作
- libevent高性能网络库源码分析——事件(event)及其接口(三)
- libevent源码学习(三)信号evsignal
- libevent源码学习(四)定时器Timer
- 【libevent】源码学习(一)--开篇
- 【libevent】源码学习(2)--配置event_base
- 【libevent】源码学习(5)--bufferevent详解
- libevent库源码学习-devpoll(/dev/poll)( linux)
- libevent库源码学习-poll/select( linux)
- kqueue(转)
- freebsd kqueue
- python 正则表达式:匹配字符串开头并打印
- 用顺序表链表分别实现栈和队列
- 001
- 你们好吗
- CPS平台License完稿
- libevent库源码学习-kqueue( freebsd) ,evport(Event ports)(Solaris 10)
- 摩斯码表
- UVA 10173 Smallest Bounding Rectangle (旋转卡壳最小面积外接矩形)
- The Wavelet Tutorial Part IV
- VS2008打开项目 无法识别工具版本 4.0
- 基于Mongodb分布式存储物理文件
- 更积极地学习
- java实现简单的单点登录
- Dtree+Jquery动态生成树节点.