美文网首页物联网相关技术研究物联网loT从业者HCLAB
MT7688学习笔记(4)——使用libevent创建WebSe

MT7688学习笔记(4)——使用libevent创建WebSe

作者: Leung_ManWah | 来源:发表于2018-08-21 18:11 被阅读3次

    一、简介

    Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。

    Libevent 包括事件管理、缓存管理、DNS、HTTP、缓存事件几大部分。事件管理包括各种IO(socket)、定时器、信号等事件;缓存管理是指evbuffer功能;DNS是libevent提供的一个异步DNS查询功能;HTTP是libevent的一个轻量级http实现,包括服务器和客户端。libevent也支持ssl,这对于有安全需求的网络程序非常的重要,但是其支持不是很完善,比如http server的实现就不支持ssl。

    二、结构分析

    2.1 结构体evhttp

    struct evhttp
    {
      /* Next vhost, if this is a vhost. */
      TAILQ_ENTRY(evhttp) next_vhost;
    
      /* All listeners for this host */ 
      TAILQ_HEAD(boundq, evhttp_bound_socket) sockets; 
      TAILQ_HEAD(httpcbq, evhttp_cb) callbacks; 
    
      /* All live connections on this host. */ 
      struct evconq connections; 
      TAILQ_HEAD(vhostsq, evhttp) virtualhosts; 
      TAILQ_HEAD(aliasq, evhttp_server_alias) aliases;
     
      /* NULL if this server is not a vhost */ 
      char *vhost_pattern; 
      int timeout; 
      size_t default_max_headers_size; 
      ev_uint64_t default_max_body_size; 
    
      /* Bitmask of all HTTP methods that we accept and pass to user * callbacks. */ 
      ev_uint16_t allowed_methods; 
    
      /* Fallback callback if all the other callbacks for this connection don't match. */ 
      void (*gencb)(struct evhttp_request *req, void *); 
      void *gencbarg; 
      struct event_base *base; 
    };
    

    值得关注的有两个成员:
      callbacks,一个链表,存放用户定义的回调函数
      connections,一个链表,存放所有连接,每个连接对应一个evhttp_connection

    2.2 结构体evhttp_connection

    /* A client or server connection. */
    struct evhttp_connection 
    {
        /* we use this tailq only if this connection was created for an http server */
        TAILQ_ENTRY(evhttp_connection) next;
    
        evutil_socket_t fd;
        struct bufferevent *bufev;
    
        struct event retry_ev;        /* for retrying connects */
    
        char *bind_address;           /* address to use for binding the src */
        u_short bind_port;            /* local port for binding the src */
    
        char *address;                /* address to connect to */
        u_short port;
    
        size_t max_headers_size;
        ev_uint64_t max_body_size;
    
        int flags;
        #define EVHTTP_CON_INCOMING    0x0001     /* only one request on it ever */
        #define EVHTTP_CON_OUTGOING    0x0002     /* multiple requests possible */
        #define EVHTTP_CON_CLOSEDETECT  0x0004    /* detecting if persistent close */
    
        int timeout;            /* timeout in seconds for events */
        int retry_cnt;          /* retry count */
        int retry_max;          /* maximum number of retries */
    
        enum evhttp_connection_state state;
    
        /* for server connections, the http server they are connected with */
        struct evhttp *http_server;
    
        TAILQ_HEAD(evcon_requestq, evhttp_request) requests;
    
        void (*cb)(struct evhttp_connection *, void *);
        void *cb_arg;
    
        void (*closecb)(struct evhttp_connection *, void *);
        void *closecb_arg;
    
        struct deferred_cb read_more_deferred_cb;
    
        struct event_base *base;
        struct evdns_base *dns_base;
    };
    

    值得关注的有两个成员:
      bufev,对应一个bufferevent
      requests,一个链表,存放该连接上的所有请求,每个请求对应evhttp_request

    2.3 结构体evhttp_request

    struct evhttp_request 
    {
      #if defined(TAILQ_ENTRY)
      TAILQ_ENTRY(evhttp_request) next;
      #else
      struct 
      {
        struct evhttp_request *tqe_next;
        struct evhttp_request **tqe_prev;
      } next;
      #endif
    
      /* the connection object that this request belongs to */
      struct evhttp_connection *evcon;
      int flags;
      /** The request obj owns the evhttp connection and needs to free it */
      #define EVHTTP_REQ_OWN_CONNECTION    0x0001
      /** Request was made via a proxy */
      #define EVHTTP_PROXY_REQUEST         0x0002
      /** The request object is owned by the user; the user must free it */
      #define EVHTTP_USER_OWNED            0x0004
      /** The request will be used again upstack; freeing must be deferred */
      #define EVHTTP_REQ_DEFER_FREE        0x0008
      /** The request should be freed upstack */
      #define EVHTTP_REQ_NEEDS_FREE        0x0010
    
      struct evkeyvalq *input_headers;  // 保存客户端请求的HTTP headers(key-value pairs)
      struct evkeyvalq *output_headers; // 保存将要发送到客户端的HTTP headers(key-value pairs)
    
      /* address of the remote host and the port connection came from */
      char *remote_host;
      ev_uint16_t remote_port;
    
      /* cache of the hostname for evhttp_request_get_host */
      char *host_cache;
    
      enum evhttp_request_kind kind;   // 可以是EVHTTP_REQUEST或EVHTTP_RESPONSE
      enum evhttp_cmd_type type;       // 可以是EVHTTP_REQ_GET, EVHTTP_REQ_POST或EVHTTP_REQ_HEAD
    
      size_t headers_size;
      size_t body_size;
    
      char *uri;                       /* uri after HTTP request was parsed */
      struct evhttp_uri *uri_elems;    // 客户端请求的uri
      char major;                      /* HTTP Major number */
      char minor;                      /* HTTP Minor number */
    
      int response_code;               /* HTTP Response code */
      char *response_code_line;        /* Readable response */
    
      struct evbuffer *input_buffer;   // 客户端POST的数据
      ev_int64_t ntoread;
      unsigned chunked:1,              /* a chunked request */
      userdone:1;                      /* the user has sent all data */
    
      struct evbuffer *output_buffer;  // 输出到客户端的数据
      /* Callback */
      void (*cb)(struct evhttp_request *, void *);
      void *cb_arg;
    
      /*
      * Chunked data callback - call for each completed chunk if
      * specified.  If not specified, all the data is delivered via
      * the regular callback.
      */
      void (*chunk_cb)(struct evhttp_request *, void *);
    };
    

    值得注意的是:
      每个请求有自己的输入缓冲input_buffer、输出缓冲output_buffer。

    总结一下evhttp:
      1. 一个evhttp使用一个链表存放多个evhttp_connection,每个evhttp_connection使用链表存放多个evhttp_request。
      2. 每个evhttp_connection包含一个bufferevent,每个evhttp_request包含两个evbuffer,用于输入输出缓冲。

    2.3.1 结构体evkeyvalq

    定义参看:http://monkey.org/~provos/libevent/doxygen-1.4.10/event_8h-source.html

    struct evkeyvalq被定义为TAILQ_HEAD (evkeyvalq, evkeyval);
    即struct evkeyval类型的tail queue。需要在代码之前包含

    #include <sys/queue.h>
    #include <event.h>
    

    2.3.2 结构体evkeyval

    struct evkeyval为key-value queue(队列结构),主要用来保存HTTP headers,也可以被用来保存parse uri参数的结果。

    /* Key-Value pairs.  Can be used for HTTP headers but also for query argument parsing. */
    struct evkeyval 
    {
      TAILQ_ENTRY(evkeyval) next; //队列
      char *key;
      char *value;
    };
    

    宏TAILQ_ENTRY(evkeyval)被定义为:

    #define TAILQ_ENTRY(type)
    struct
    {
      struct type *tqe_next;      //next element
      struct type **tqe_prev;     //address of previous next element
    }
    

    2.3.3 结构体stuct evbuffer

    定义参看:http://monkey.org/~provos/libevent/doxygen-1.4.10/event_8h-source.html
    该结构体用于input和output的buffer。

    /* These functions deal with buffering input and output */
    struct evbuffer 
    {
      u_char *buffer;
      u_char *orig_buffer;
      
      size_t misalign;
      size_t totallen;
      size_t off;
      
      void (*cb)(struct evbuffer *, size_t, size_t, void *);
      void *cbarg;
    };
    

    另外定义宏方便获取evbuffer中保存的内容和大小:

    #define EVBUFFER_LENGTH(x)      (x)->off
    #define EVBUFFER_DATA(x)        (x)->buffer
    

    例如,获取客户端POST数据的内容和大小:

    EVBUFFER_DATA(res->input_buffer);
    EVBUFFER_LENGTH(res->input_buffer);
    

    另外struct evbuffer用如下函数创建添加和释放:

    struct evbuffer *buf;
    buf = evbuffer_new();
    //往buffer中添加内容
    evbuffer_add_printf(buf, "It works! you just requested: %s\n", req->uri);   //Append a formatted string to the end of an evbuffer.
    //将内容输出到客户端
    evhttp_send_reply(req, HTTP_OK, "OK", buf);
    //释放掉buf
    evbuffer_free(buf);
    

    三、API

    3.1 evbuffer

    3.1.1 evbuffer_new

    struct evbuffer *evbuffer_new(void);
    

    evbuffer_new()分配和返回一个新的空evbuffer。

    3.1.2 evbuffer_free

    void evbuffer_free(struct evbuffer *buf);
    

    evbuffer_free()释放evbuffer和其内容。

    3.1.3 evbuffer_add

    int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);
    
    • 用途:添加data处的datalen字节到buf的末尾。
    • 结果:0表示成功,-1表示失败

    3.1.4 evbuffer_add_printf

    int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ···);
    
    • 用途:添加格式化的数据到buf末尾。
    • 参数:格式参数和其他参数的处理与C库函数printf相同。
    • 结果:返回添加的字节数。

    3.1.5 evbuffer_get_length

    size_t evbuffer_get_length(const struct evbuffer *buf);
    

    函数返回evbuffer存储的字节数。

    3.1.6 evbuffer_pullup

    unsigned char *evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size);
    

    “线性化”buf前面的size字节,必要时将进行复制或者移动,以保证这些字节是连续的,占据相同的内存块。如果size是负的,函数会线性化整个缓冲区。如果size大于缓冲区中的字节数,函数返回NULL。否则,evbuffer_pullup()返回指向buf中首字节的指针。

    调用evbuffer_pullup()时使用较大的size参数可能会非常慢,因为这可能需要复制整个缓冲区的内容。

    3.2 evhttp

    3.2.1 evhttp_add_header

    int evhttp_add_header(struct evkeyvalq *headers, const char *key, const char *);
    
    • 用途:为已存在的http请求头部添加另外的头部,
    • 参数:(1)headers,为http请求的output_headers;
         (2)key,为headers的名字;
         (3)value,为headers的值。
    • 结果:0表示成功,-1表示失败

    3.2.2 evhttp_parse_query

    void evhttp_parse_query(const char *uri, struct evkeyvalq *args);
    

    可对uri的参数进行解析,结果保存在struct evkeyvalq的key-value pairs中。

    四、应用

    HTTP请求处理函数

    static void http_request_cb(struct evhttp_request *req, void *arg)
    {
        const char *cmdtype;
        struct evkeyvalq *headers;
        struct evkeyval *header;        // 用来保存HTTP headers的队列
        struct evbuffer *buf;
        struct evbuffer *evb = NULL;
        unsigned int request_command = evhttp_request_get_command(req);
        
        switch (request_command) 
        {
            case EVHTTP_REQ_GET: cmdtype = "GET"; break;
            case EVHTTP_REQ_POST: cmdtype = "POST"; break;
            case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break;
            case EVHTTP_REQ_PUT: cmdtype = "PUT"; break;
            case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break;
            case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break;
            case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break;
            case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break;
            case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break;
            default: cmdtype = "unknown"; break;
        }
        // Debug信息输出start-------------------------
        printf("Received a %s request for %s\nHeaders:\n", cmdtype, evhttp_request_get_uri(req));
        headers = evhttp_request_get_input_headers(req);
        for (header = headers->tqh_first; header; header = header->next.tqe_next) 
        {
            cout << "  " << header->key <<  ": " << header->value << endl;
        }
        // Debug信息输出end-------------------------
    
        evb = evbuffer_new();
    
        // Http Get请求   
        if(request_command == EVHTTP_REQ_GET)
        {
            evhttp_parse_query(evhttp_request_get_uri(req), headers);    // 解析URI的参数
            for (header = headers->tqh_first; header; header = header->next.tqe_next) 
            {
                cout << "  " << header->key <<  ": " << header->value << endl;
            }
            
            evbuffer_add_printf(evb, "<html>\n <head>\n"
                                        " <title>almWeb</title>\n"
                                        " </head>\n"
                                        " <body>\n"
                                        "  %s\n",
                                        "hello world");
            evbuffer_add_printf(evb, "</body></html>\n");
            
            evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "text/html");
        }
        // Http POST请求
        else if(request_command == EVHTTP_REQ_POST)
        {
            buf = evhttp_request_get_input_buffer(req);
            int postDataLen = evbuffer_get_length(buf);
            evbuffer_add (buf, "", 1);
            char *payload = (char *) evbuffer_pullup(buf, -1);
            int post_data_len = evbuffer_get_length(buf);
            char request_data_buf[post_data_len];
            memcpy(request_data_buf, payload, post_data_len);
            
            cout << "[post_data][" << post_data_len << "]="<<  request_data_buf << endl;
            Json::Reader reader;
            Json::Value JsonRoot;
            if(!reader.parse(request_data_buf, JsonRoot))
            {
                cout << "Json parse err" << endl;
            }
            else
            {
                if(JsonRoot.isMember("Module"))
                {
                    string strModule = JsonRoot["Module"].asString();
                    CHCIModule *pHCIModule = new CHCIModule(strModule);
                    pHCIModule->doMatch(&JsonRoot);
                    delete pHCIModule;
                }
                cout << "" << endl;
            }   
    
            evbuffer_add_printf(evb, "%s\n", JsonRoot.toStyledString().c_str()); 
            
            evbuffer_add_printf(evb, "</ul></body></html>\n");
            evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "application/json");
            evhttp_add_header(evhttp_request_get_output_headers(req), "Connection", "close");
        }
        evhttp_send_reply(req, 200, "OK", evb);
        if (evb)
        {
            evbuffer_free(evb);
        }
    }
    

    • 由 Leung 写于 2018 年 8 月 21 日

    • 参考:libevent(九)evhttp
        使用libevent编写高并发HTTP server
        libevent学习笔记【使用篇】——7. evbuffer:缓冲IO的实用功能

    相关文章

      网友评论

        本文标题:MT7688学习笔记(4)——使用libevent创建WebSe

        本文链接:https://www.haomeiwen.com/subject/oztjiftx.html