libevent库源码学习-kqueue( freebsd)

来源:互联网 发布:无权限新建数据库 编辑:程序博客网 时间:2024/05/16 09:14

kqueue 提供 kqueue()、kevent() 两个系统调用和 struct kevent 结构。

 int kqueue(void) 

生成一个内核事件队列,返回该队列的文件描述索。其它 API 通过该描述符操作这个 kqueue。生成的多个 kqueue 的结构类似图 1 所示。

图 1. kqueue 队列结构
 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_SET
     EV_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
原创粉丝点击