您当前的位置:首页 > 电子 > 单片机

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

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

一、简介

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);
	}
}

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