2025年5月28日 星期三 乙巳(蛇)年 三月初一 设为首页 加入收藏
rss
您当前的位置:首页 > 电子 > 单片机

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

时间:03-02来源:作者:点击数:72

一、简介

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.cdsy.xyz/computer/programme/vc/230302/cd41085.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);
  • }
  • }

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门