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 所示。

图 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

    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()

    二.例子

    [cpp] view plaincopyprint?
    1. str_srv.c:  
    2. /* 用 Event ports 实现的 UNPv1 ECHO Server 例子程序。 
    3.  * 
    4.  * 编译命令: 
    5.  *   gcc -o str_srv str_srv.c -lsocket 
    6.  * 
    7.  *                   flyriver 2004.11.02 
    8.  */  
    9. #include <stdio.h>  
    10. #include <netinet/in.h>  
    11. #include <sys/types.h>  
    12. #include <sys/socket.h>  
    13. #include <arpa/inet.h>  
    14. #include <sys/poll.h>  
    15. #include <errno.h>  
    16. #include <stdlib.h>  
    17. #include <limits.h>  
    18. #include <fcntl.h>  
    19. #include <port.h> /* for event ports */  
    20.   
    21. #define SERV_PORT 9877  
    22. #define MAXLINE 80  
    23.   
    24. #ifndef OPEN_MAX  
    25. #define OPEN_MAX 256  
    26. #endif   
    27.   
    28. typedef void (*port_func_t)(port_event_t *);  
    29.   
    30. int portfd;  
    31. uint_t maxi;  
    32.   
    33. static void echo(port_event_t *ev)  
    34. {  
    35.     ssize_t n;  
    36.     int sockfd;  
    37.     char buf[MAXLINE];  
    38.   
    39.     sockfd = ev->portev_object;  
    40.     if (ev->portev_events & POLLIN)  
    41.     {  
    42.         if ((n = read(sockfd, buf, MAXLINE)) < 0)  
    43.         {  
    44.             perror("read");  
    45.             close(sockfd);  
    46.             maxi--;  
    47.         }  
    48.         else if (n == 0) /* connection closed by client */  
    49.         {  
    50.             close(sockfd);  
    51.             maxi--;  
    52.         }  
    53.         else  
    54.         {  
    55.             write(sockfd, buf, n);  
    56.             /* 重新关联 client fd 到 event ports */  
    57.             port_associate(portfd, PORT_SOURCE_FD, sockfd, POLLIN, echo);  
    58.         }  
    59.     }  
    60. }  
    61.   
    62. static void new_conn(port_event_t *ev)  
    63. {  
    64.     int connfd;  
    65.     socklen_t clilen;  
    66.     struct sockaddr_in cliaddr;  
    67.     int listenfd;  
    68.   
    69.     listenfd = ev->portev_object;  
    70.     clilen = sizeof(cliaddr);  
    71.     connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);  
    72.   
    73.     /* 关联 client fd 到 event ports */  
    74.     port_associate(portfd, PORT_SOURCE_FD, connfd, POLLIN, echo);  
    75.     maxi++;  
    76.   
    77.     /* 重新关联 listenfd 到 event ports */  
    78.     port_associate(portfd, PORT_SOURCE_FD, listenfd, POLLIN, new_conn);  
    79. }  
    80.   
    81. int main()  
    82. {  
    83.     int listenfd;  
    84.     struct sockaddr_in servaddr;  
    85.     int optval;  
    86.     /* variables for event ports version */  
    87.     int i;  
    88.     uint_t nready;  
    89.     port_event_t client[OPEN_MAX];  
    90.     timespec_t timeout;  
    91.     port_func_t port_func;  
    92.   
    93.     bzero(&servaddr, sizeof(servaddr));  
    94.     listenfd = socket(AF_INET, SOCK_STREAM, 0);  
    95.     servaddr.sin_family = AF_INET;  
    96.     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
    97.     servaddr.sin_port = htons(SERV_PORT);  
    98.   
    99.     setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, 4);  
    100.     bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));  
    101.     listen(listenfd, 5);  
    102.   
    103.     /* 创建 event ports */  
    104.     portfd = port_create();  
    105.     /* 关联 listenfd 到 event ports */  
    106.     port_associate(portfd, PORT_SOURCE_FD, listenfd, POLLIN, new_conn);  
    107.     maxi++;  
    108.     timeout.tv_sec = 0;  
    109.     timeout.tv_nsec = 50000000; /* 50 毫秒 */  
    110.     for (;;)  
    111.     {  
    112.         /* get events here*/  
    113.         nready = maxi;  
    114.         if (port_getn(portfd, client, OPEN_MAX, &nready, &timeout) < 0  
    115.                 && errno != ETIME)  
    116.         {  
    117.             if (errno == EINTR)  
    118.                 continue;  
    119.             else  
    120.             {  
    121.                 perror("port_getn");  
    122.                 return -1;  
    123.             }  
    124.         }  
    125.         for (i = 0; i < nready; i++)  
    126.         {  
    127.             if (client[i].portev_source == PORT_SOURCE_FD)  
    128.             {  
    129.                 port_func = (port_func_t)client[i].portev_user;  
    130.                 port_func(&client[i]);  
    131.             }  
    132.         }  
    133.     }  
    134.     close(portfd);  
    135.   
    136.     return 0;  
    137. }  



    参考: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

    原创粉丝点击