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

一、简介

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

File Reference参看:https://monkey.org/~provos/libevent/doxygen-1.4.10/files.html

3.1 event.h

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.1.7 event_base_new

struct event_base *event_base_new(void); 

Initialize the event API.

Use event_base_new() to initialize a new event base, but does not set the current_base global. If using only event_base_new(), each event added must have an event base set with event_base_set()

3.1.8 event_base_dispatch

int event_base_dispatch(struct event_base *eb);
  • 用途:Threadsafe event dispatching loop.
  • 参数:eb the event_base structure returned by event_init()
  • 结果:0表示成功,-1表示失败

3.2 evhttp.h

3.2.1 evhttp_new

struct evhttp *evhttp_new(struct event_base *base);
  • 用途:Create a new HTTP server.
  • 参数:base (optional) the event base to receive the HTTP events.
  • 结果:a pointer to a newly initialized evhttp server structure.

3.2.2 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.3 evhttp_parse_query

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

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

3.2.4 evhttp_set_gencb

void evhttp_set_gencb(struct evhttp *, void(*)(struct evhttp_request *, void *), void *); 

Set a callback for all requests that are not caught by specific callbacks.

3.2.5 evhttp_bind_socket

int evhttp_bind_socket(struct evhttp *http, const char *address, ev_uint16_t port);
  • 用途:绑定http server到一个指定的ip地址和端口,可重复调用该函数来绑定到同一个地址的不同端口。
  • 参数:(1)http,为待绑定的http server指针;
       (2)address,为待绑定的ip地址;
       (3)port,为待绑定的端口号。
  • 结果:0表示成功,-1表示失败
  • 备注:跟此函数类似的一个函数为evhttp_bind_socket_with_handle,其声明是struct evhttp_bound_socket *evhttp_bind_socket_with_handle(struct evhttp *http, const char *address, ev_uint16_t port); 与evhttp_bind_socket唯一不同的地方是返回值不同,它返回了一个socket句柄。

3.3 event2/util.h

3.3.1 evutil_inet_ntop

const char *evutil_inet_ntop(int af, const void *src, char *dst, size_t len);
  • 用途:要格式化IPv4地址,调用evutil_inet_ntop(),设置af为AF_INET,src指向in_addr结构体,dst指向大小为len的字符缓冲区。
  • 结果:返回dst的指针表示成功,返回NULL表示失败

四、流程

  1. 调用event_base_new函数得到一个event base对象。

  2. 调用evhttp_new函数得到一个evhttp对象。

  3. 调用evhttp_set_cb、evthttp_set_gencb设置回调函数和通用回调函数。

  4. 调用evhttp_bind_socket_with_handle函数设置监听端口。

  5. 打印监听端口信息。

  6. 调用event_base_dispatch进入事件循环。

五、应用

HTTP_Server例子参看:https://www.cnblogs.com/lit10050528/p/6168465.html

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的实用功能
    libevent源码分析:http-server例子

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容