zoukankan      html  css  js  c++  java
  • 轻量级网络库libevent初探

      本文是关于libevent库第一篇博文,主要由例子来说明如何利用该库。后续博文再深入研究该库原理。

    libevent库简介

      就如libevent官网上所写的“libevent - an event notification library”,libevent就是一个基于事件通知机制的库,支持/dev/poll、kqueue、event ports、select、poll和epoll事件机制,也因此它是一个跨操作系统的库(支持Linux、*BSD、Mac OS X、Solaris、Windows等)。目前应用该库的有Chromium、Memcached、NTP、tmux等应用。

      libevent 库实际上没有更换select()、poll()或其他机制的基础,而是使用对于每个平台最高效的高性能解决方案,在其实现外加上一个包装器。

      为了实际处理每个请求,libevent 库提供一种事件机制,它作为底层网络后端的包装器。事件系统让为连接添加处理函数变得非常简便,同时降低了底层 I/O 复杂性。这是 libevent 系统的核心。

      libevent 库的其他组件提供其他功能,包括缓冲的事件系统(用于缓冲发送到客户端/从客户端接收的数据)以及 HTTP、DNS 和 RPC 系统的核心实现。

      另外,libevent库非常轻量级,这让我们学习它的源码难度低了不少。关于源码分析具体可参考:

      Libevent源码分析

      libevent源码深度剖析

      如果要生成libevent库的文档,可参考博文使用Doxygen生成libevent document(2.0.15)-- CHM格式。 

    回显服务端示例

    简易流程

      创建 libevent 服务器的基本方法是,注册当发生某一操作(比如接受来自客户端的连接)时应该执行的函数,然后调用主事件循环event_base_dispatch()。执行过程的控制由 libevent系统处理。注册事件和将调用的函数之后,事件系统开始自治;在应用程序运行时,可以在事件队列中添加(注册)或删除(取消注册)事件。事件注册非常方便,可以通过它添加新事件以处理新打开的连接,从而构建灵活的网络处理系统。

      例如,可以打开一个监听套接字,然后注册一个回调函数,每当需要调用accept()函数以打开新连接时调用这个回调函数,这样就创建了一个网络服务器。下边所示的代码片段说明了这个基本过程:

     1 int main(int argc, char **argv)
     2 {
     3     /* Declare a socket file descriptor. */
     4     evutil_socket_t listenfd;
     5 
     6     /* Setup listening socket */
     7 
     8     /* Make the listen socket reuseable and non-blocking. */
     9     evutil_make_listen_socket_reuseable(listenfd);
    10     evutil_make_socket_nonblocking(listenfd);
    11 
    12     /* Declare an event_base to host events. */
    13     struct event_base *base = event_base_new();
    14 
    15     /* Register listen event. */
    16     struct event *listen_event;
    17     listen_event = event_new(base, listenfd, EV_READ | EV_PERSIST, do_accetp, (void *)base);
    18     event_add(listen_event, NULL);
    19 
    20     /* Start the event loop. */
    21     event_base_dispatch(base);
    22 
    23     /* End. */
    24     close(listenfd);
    25 return 0; 26 }

      下边详细介绍上边程序中用到的libevent中的API:

      1)evutil_socket_t 定义于Util.h头文件中,用于跨平台表示socket的ID(在Linux下表示的是其文件描述符),如下所示:

    /**
     * A type wide enough to hold the output of "socket()" or "accept()".  On
     * Windows, this is an intptr_t; elsewhere, it is an int. */
    #ifdef WIN32
    #define evutil_socket_t intptr_t
    #else
    #define evutil_socket_t int
    #endif
    View Code

      2)evutil_make_listen_socket_reuseable 函数声明于Util.h,实现于Evutil.c,用于跨平台将socket设置为可重用(实际上是将端口设为可重用,具体可参照博文Linux 套接字编程中的 5 个隐患中的第3个隐患),具体定义如下:

    int
    evutil_make_listen_socket_reuseable(evutil_socket_t sock)
    {
    #ifndef WIN32
        int one = 1;
        /* REUSEADDR on Unix means, "don't hang on to this address after the
         * listener is closed."  On Windows, though, it means "don't keep other
         * processes from binding to this address while we're using it. */
        return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one,
            (ev_socklen_t)sizeof(one));
    #else
        return 0;
    #endif
    }
    View Code

      同样,evutil_make_socket_nonblocking函数也声明于Util.h,实现于Evutil.c,用于跨平台将socket设置为非阻塞,具体定义如下:

    int
    evutil_make_socket_nonblocking(evutil_socket_t fd)
    {
    #ifdef WIN32
        {
            u_long nonblocking = 1;
            if (ioctlsocket(fd, FIONBIO, &nonblocking) == SOCKET_ERROR) {
                event_sock_warn(fd, "fcntl(%d, F_GETFL)", (int)fd);
                return -1;
            }
        }
    #else
        {
            int flags;
            if ((flags = fcntl(fd, F_GETFL, NULL)) < 0) {
                event_warn("fcntl(%d, F_GETFL)", fd);
                return -1;
            }
            if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
                event_warn("fcntl(%d, F_SETFL)", fd);
                return -1;
            }
        }
    #endif
        return 0;
    }
    View Code

       3)event_base结构体定义在event_internal.h中,它记录了所有的等待和已激活的事件,并当有事件被激活时通知调用者。默认地,我们用event_base_new函数就可以新建一个event_base对象。event_base_new函数的定义如下:

    struct event_base *
    event_base_new(void)
    {
        struct event_base *base = NULL;
        struct event_config *cfg = event_config_new();
        if (cfg) {
            base = event_base_new_with_config(cfg);
            event_config_free(cfg);
        }
        return base;
    }

      也就是说实际上该函数调用了event_base_new_with_config来创建event_base对象,所以我们也可以利用event_config_new和event_base_new_with_config定制event_base对象。

      4)event结构体定义在event_struct.h文件中,主要记录事件的相关属性。event_new函数用于创建一个event对象,具体定义如下:

    struct event *
    event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg)
    {
        struct event *ev;
        ev = mm_malloc(sizeof(struct event));
        if (ev == NULL)
            return (NULL);
        if (event_assign(ev, base, fd, events, cb, arg) < 0) {
            mm_free(ev);
            return (NULL);
        }
    
        return (ev);
    }
    // Parameters:
        // base            the event base to which the event should be attached.
        // fd              the file descriptor or signal to be monitored, or -1.
        // events          desired events to monitor: bitfield of EV_READ, EV_WRITE, EV_SIGNAL, EV_PERSIST, EV_ET.
        // callback        callback function to be invoked when the event occurs
        // callback_arg    an argument to be passed to the callback function
    // Returns:
        // a newly allocated struct event that must later be freed with event_free().
    View Code

      在上边程序中,cb是回调函数,其原型如下:

    /**
       A callback function for an event.
    
       It receives three arguments:
    
       @param fd An fd or signal
       @param events One or more EV_* flags
       @param arg A user-supplied argument.
    
       @see event_new()
     */
    typedef void (*event_callback_fn)(evutil_socket_t, short, void *);

      5)event_base_dispatch函数开启事件轮询(event_base_loop提供同样功能,不过更为灵活,实际event_base_dispatch只是event_base_loop的特例),定义如下:

    int
    event_base_dispatch(struct event_base *event_base)
    {
        return (event_base_loop(event_base, 0));
    }

    实际例子 

       一个完整的服务器端的程序如下:

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <errno.h>
      4 #include <assert.h>
      5 #include <unistd.h>
      6 #include <netinet/in.h>
      7 #include <sys/socket.h>
      8 #include <arpa/inet.h>
      9 #include <sys/types.h>
     10 
     11 #include <event2/event.h>
     12 #include <event2/bufferevent.h>
     13 
     14 #define SERV_PORT 9877
     15 #define LISTEN_BACKLOG 32
     16 #define MAX_LINE 1024
     17 
     18 void do_accetp(evutil_socket_t listenfd, short event, void *arg);
     19 void read_cb(struct bufferevent *bev, void *arg);
     20 void error_cb(struct bufferevent *bev, short event, void *arg);
     21 void write_cb(struct bufferevent *bev, void *arg);
     22 
     23 int main(int argc, int **argv)
     24 {
     25     evutil_socket_t listenfd;
     26     if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
     27     {
     28         perror("socket
    ");
     29         return 1;
     30     }
     31 
     32     struct sockaddr_in servaddr;
     33     bzero(&servaddr, sizeof(servaddr));
     34     servaddr.sin_family = AF_INET;
     35     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
     36     servaddr.sin_port = htons(SERV_PORT);
     37 
     38     if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
     39     {
     40         perror("bind
    ");
     41         return 1;
     42     }
     43     if(listen(listenfd, LISTEN_BACKLOG) < 0)
     44     {
     45         perror("listen
    ");
     46         return 1;
     47     }
     48 
     49     printf("Listening...
    ");
     50 
     51     evutil_make_listen_socket_reuseable(listenfd);
     52     evutil_make_socket_nonblocking(listenfd);
     53 
     54     struct event_base *base = event_base_new();
     55     if(base == NULL)
     56     {
     57         perror("event_base
    ");
     58         return 1;
     59     }
     60     const char *eventMechanism = event_base_get_method(base);
     61     printf("Event mechanism used is %s
    ", eventMechanism);
     62 
     63     struct event *listen_event;
     64     listen_event = event_new(base, listenfd, EV_READ | EV_PERSIST, do_accetp, (void *)base);
     65     event_add(listen_event, NULL);
     66     event_base_dispatch(base);
     67 
     68     if(close(listenfd) < 0)
     69     {
     70         perror("close
    ");
     71         return 1;
     72     }
     73     printf("The End
    ");
     74     return 0;
     75 }
     76 
     77 void do_accetp(evutil_socket_t listenfd, short event, void *arg)
     78 {
     79     struct event_base *base = (struct event_base *)arg;
     80     evutil_socket_t fd;
     81     struct sockaddr_in cliaddr;
     82     socklen_t clilen;
     83     fd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);
     84     if(fd < 0)
     85     {
     86         perror("accept
    ");
     87         return;
     88     }
     89     if(fd > FD_SETSIZE)
     90     {
     91         perror("fd > FD_SETSIZE");
     92         if(close(fd) < 0)
     93         {
     94             perror("close
    ");
     95             return;
     96         }
     97         return;
     98     }
     99     
    100     printf("Accept: fd = %u
    ", fd);
    101     
    102     struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    103     bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
    104     bufferevent_enable(bev, EV_READ | EV_WRITE | EV_PERSIST);
    105 }
    106     
    107 void read_cb(struct bufferevent *bev, void *arg)
    108 {
    109     char line[MAX_LINE + 1];
    110     int n;
    111     evutil_socket_t fd = bufferevent_getfd(bev);
    112 
    113     while((n = bufferevent_read(bev, line, MAX_LINE)) > 0)
    114     {
    115         line[n] = '';
    116         printf("fd = %u, read line: %s", fd, line);
    117         bufferevent_write(bev, line, n);
    118     }
    119 }
    120 
    121 void error_cb(struct bufferevent *bev, short event, void *arg)
    122 {
    123     evutil_socket_t fd = bufferevent_getfd(bev);
    124     printf("fd = %u, ", fd);
    125     if(event & BEV_EVENT_TIMEOUT)
    126         printf("Time out.
    ");  // if bufferevent_set_timeouts() is called
    127     else if(event & BEV_EVENT_EOF)
    128         printf("Connection closed.
    ");
    129     else if(event & BEV_EVENT_ERROR)
    130         printf("Some other error.
    ");
    131     bufferevent_free(bev);
    132 }
    133 
    134 void write_cb(struct bufferevent *bev, void *arg)
    135 {
    136     // leave blank
    137 }
    View Code

      注意:在Linux下编译时需要加libevent静态库event,即gcc ... -levent。

      上边程序中用到的bufferevent值得再说明一下。bufferevent由一个底层的传输端口(如套接字)、一个读取缓冲区和一个写入缓冲区组成。与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调。详细可参考博文libevent参考手册第六章:bufferevent:概念和入门

      利用bufferevent的简易流程如下:

    1 struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    2 bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
    3 bufferevent_enable(bev, EV_READ | EV_WRITE | EV_PERSIST);

       bufferevent_setcb使得我们可以定制我们自己的回调函数,这里我们只用到了读和错误回调函数。最后,我们要调用bufferevent_enable来使得bufferevent启动。

    客户端示例

      客户端用到的libevent的API跟服务端的基本一样。具体程序如下:

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <errno.h>
      4 #include <unistd.h>
      5 #include <string.h>
      6 #include <netinet/in.h>
      7 #include <sys/socket.h>
      8 #include <arpa/inet.h>
      9 #include <sys/types.h>
     10 
     11 #include <event2/event.h>
     12 #include <event2/bufferevent.h>
     13 
     14 #define SERV_PORT 9877
     15 #define MAX_LINE 1024
     16 
     17 void cmd_msg_cb(int fd, short event, void *arg);
     18 void read_cb(struct bufferevent *bev, void *arg);
     19 void error_cb(struct bufferevent *bev, short event, void *arg);
     20 
     21 int main(int argc, char *argv[])
     22 {
     23     if(argc < 2)
     24     {
     25         perror("usage: echocli <IPadress>");
     26         return 1;
     27     }
     28 
     29     evutil_socket_t sockfd;
     30     if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
     31     {
     32         perror("socket
    ");
     33         return 1;
     34     }
     35 
     36     struct sockaddr_in servaddr;
     37     bzero(&servaddr, sizeof(servaddr));
     38     servaddr.sin_family = AF_INET;
     39     servaddr.sin_port = htons(SERV_PORT);
     40     if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 1)
     41     {
     42         perror("inet_ntop
    ");
     43         return 1;
     44     }
     45     if(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
     46     {
     47         perror("connect
    ");
     48         return 1;
     49     }
     50     evutil_make_socket_nonblocking(sockfd);
     51 
     52     printf("Connect to server sucessfully!
    ");
     53 
     54     struct event_base *base = event_base_new();
     55     if(base == NULL)
     56     {
     57         perror("event_base
    ");
     58         return 1;
     59     }
     60     const char *eventMechanism = event_base_get_method(base);
     61     printf("Event mechanism used is %s
    ", eventMechanism);
     62     printf("sockfd = %d
    ", sockfd);
     63 
     64     struct bufferevent *bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);
     65     
     66     struct event *ev_cmd;
     67     ev_cmd = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, cmd_msg_cb, (void *)bev);
     68     event_add(ev_cmd, NULL);
     69     
     70     bufferevent_setcb(bev, read_cb, NULL, error_cb, (void *)ev_cmd);
     71     bufferevent_enable(bev, EV_READ | EV_PERSIST);
     72     
     73     event_base_dispatch(base);
     74 
     75     printf("The End.");
     76     return 0;
     77 }
     78 
     79 void cmd_msg_cb(int fd, short event, void *arg)
     80 {
     81     char msg[MAX_LINE];
     82     int nread = read(fd, msg, sizeof(msg));
     83     if(nread < 0)
     84     {
     85         perror("stdio read fail
    ");
     86         return;
     87     }
     88 
     89     struct bufferevent *bev = (struct bufferevent *)arg;
     90     bufferevent_write(bev, msg, nread);
     91 }
     92 
     93 void read_cb(struct bufferevent *bev, void *arg)
     94 {
     95     char line[MAX_LINE + 1];
     96     int n;
     97     evutil_socket_t fd = bufferevent_getfd(bev);
     98 
     99     while((n = bufferevent_read(bev, line, MAX_LINE)) > 0)
    100     {
    101         line[n] = '';
    102         printf("fd = %u, read from server: %s", fd, line);
    103     }
    104 }
    105 
    106 void error_cb(struct bufferevent *bev, short event, void *arg)
    107 {
    108     evutil_socket_t fd = bufferevent_getfd(bev);
    109     printf("fd = %u, ", fd);
    110     if(event & BEV_EVENT_TIMEOUT)
    111         printf("Time out.
    ");  // if bufferevent_set_timeouts() is called
    112     else if(event & BEV_EVENT_EOF)
    113         printf("Connection closed.
    ");
    114     else if(event & BEV_EVENT_ERROR)
    115         printf("Some other error.
    ");
    116     bufferevent_free(bev);
    117 
    118     struct event *ev = (struct event *)arg;
    119     event_free(ev);
    120 }
    View Code

    参考资料

      libevent入门

      A tiny introduction to asynchronous IO

      使用 libevent 和 libev 提高网络应用性能

        libevent参考手册第六章:bufferevent:概念和入门

  • 相关阅读:
    java基础知识回顾之javaIO类---InputStreamReader和OutputStreamWriter转化流
    java基础知识回顾之javaIO类---FileInputStream和FileOutputStream字节流复制图片
    基本知识《一》继承初始化过程
    java调用matlab函数
    Dubbo基础篇-zookeeper安装(单点)
    《转》从0到100——知乎架构变迁史
    算法
    【转】基于LDA的Topic Model变形
    《转》探寻微博背后的大数据原理:微博推荐算法简述
    一个完整推荐系统的设计实现-以百度关键词搜索推荐为例
  • 原文地址:https://www.cnblogs.com/xiehongfeng100/p/4686221.html
Copyright © 2011-2022 走看看