1 简介
- 在libevent实现了reactor框架&evbuffer&bufferevent特性后,开发者便可以开发任何tcp或udp程序了,libevent官方在此网络库的基础之上,实现了HTTP和DNS以及RPC,这里对HTTP着重介绍。
- libevent 的 HTTP 框架提供了一种轻量级、异步的方式来处理 HTTP 请求和响应,广泛用于编写简单的 HTTP 服务器、代理服务器或其他与 HTTP 协议相关的程序。它构建在 libevent 的事件机制之上,并充分利用了事件驱动和异步 I/O 的优势,能够高效地处理高并发的 HTTP 请求。
2 原理
2.1 HTTP 框架的核心组件
2.1.1 evhttp 结构
evhttp 是 libevent HTTP 框架中的核心结构,表示一个 HTTP 服务器。它负责监听指定端口,并管理 HTTP 请求和响应的处理。
该结构与 event_base 关联,利用 libevent 的事件循环处理客户端连接和 HTTP 请求。
2.1.2 evhttp_request 结构
evhttp_request 表示单个 HTTP 请求,每当一个新的 HTTP 请求到达服务器时,libevent 会生成一个 evhttp_request 对象。
该对象包含了 HTTP 请求的头部信息、URI、方法等,还包括一个指向 evbuffer 的指针,存储请求的内容。
2.1.3 evbuffer 结构
evbuffer 是 libevent 的核心数据缓冲区,用于存储传入和传出的数据。在 HTTP 框架中,evbuffer 用于存储 HTTP 请求体和响应体。
HTTP 请求处理完成后,数据会被写入到 evbuffer 中,然后通过网络发送到客户端。
2.1.4 事件驱动和异步处理
libevent 的 HTTP 框架完全基于事件驱动模型。当有新的 HTTP 请求到达时,它通过事件回调机制来处理该请求。
这意味着 HTTP 服务器不会阻塞在某个请求的处理中,而是可以同时处理多个请求,极大提高了并发性能。
2.2 HTTP 框架的工作流程
2.2.1 创建 HTTP 服务器
- 使用 evhttp_new() 创建一个新的 HTTP 服务器对象,并将其与事件基础 event_base 关联。
2.2.2 绑定监听端口
- 调用 evhttp_bind_socket() 或 evhttp_bind_socket_with_handle() 来绑定服务器的监听端口。
- 服务器开始监听传入的 HTTP 请求,并通过 libevent 的事件循环来检测新的连接。
2.2.3 处理 HTTP 请求
服务器注册一个回调函数(evhttp_set_cb() 或 evhttp_set_gencb()),用于处理客户端发送的 HTTP 请求。
当服务器接收到请求时,它会生成一个 evhttp_request 对象并调用相应的回调函数。
回调函数可以从 evhttp_request 中提取请求信息(如 URL、头部和内容),进行处理后构造响应。
2.2.4 发送 HTTP 响应
回调函数处理完请求后,使用 evhttp_send_reply() 发送 HTTP 响应。
响应数据通过 evbuffer 缓冲区传输,可以构建动态内容或发送文件。
2.2.5 事件循环
- 服务器的所有操作(包括接收连接、读写数据、处理请求和发送响应)都在 libevent 的事件循环中完成。通过异步处理机制,服务器能够高效响应多个请求,而不会因某个请求的处理时间长而阻塞其他请求。
2.3 HTTP 框架的主要 API
2.3.1 创建和销毁 HTTP 服务器
- struct evhttp *evhttp_new(struct event_base *base):创建 HTTP 服务器对象,并与事件基础 event_base 关联。
- void evhttp_free(struct evhttp *http):销毁 HTTP 服务器对象,释放资源。
2.3.2 监听端口
- int evhttp_bind_socket(struct evhttp *http, const char *address, unsigned short port):绑定指定地址和端口,开始监听 HTTP 请求。
2.3.3 注册回调函数
- int evhttp_set_cb(struct evhttp *http, const char *path, void (*cb)(struct evhttp_request *, void *), void *cb_arg):为特定路径注册回调函数。
- void evhttp_set_gencb(struct evhttp *http, void (*cb)(struct evhttp_request *, void *), void *cb_arg):为所有未匹配路径的请求设置通用回调函数。
2.3.4 发送响应
- void evhttp_send_reply(struct evhttp_request *req, int code, const char *reason, struct evbuffer *dat):发送 HTTP 响应,其中 code 是 HTTP 状态码,reason 是状态码的描述,dat 是响应内容。
2.3.5 解析和构造请求
- const char *evhttp_request_uri(struct evhttp_request *req):获取请求的 URI。
- struct evkeyvalq *evhttp_request_get_input_headers(struct evhttp_request *req):获取请求头部信息。
- struct evbuffer *evhttp_request_get_input_buffer(struct evhttp_request *req):获取请求的内容(body)。
- struct evbuffer *evhttp_request_get_output_buffer(struct evhttp_request *req):获取用于构建响应的缓冲区。
2.3.6 具体示例
以下是使用 libevent HTTP 框架编写的一个简单 HTTP 服务器示例:
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <stdio.h>
#include <string.h>
// 回调函数,处理 HTTP 请求
void request_handler(struct evhttp_request *req, void *arg) {
struct evbuffer *buf = evbuffer_new();
if (!buf) {
puts("Failed to create response buffer");
return;
}
// 设置 HTTP 响应内容
evbuffer_add_printf(buf, "Hello, World!");
// 发送 HTTP 响应
evhttp_send_reply(req, 200, "OK", buf);
// 释放资源
evbuffer_free(buf);
}
int main() {
struct event_base *base;
struct evhttp *http;
// 创建一个新的 event_base
base = event_base_new();
if (!base) {
puts("Couldn't create an event_base");
return 1;
}
// 创建 HTTP 服务器
http = evhttp_new(base);
if (!http) {
puts("Couldn't create evhttp. Exiting.");
return 1;
}
// 绑定到 8080 端口,监听所有 IP 地址
if (evhttp_bind_socket(http, "0.0.0.0", 8080) != 0) {
puts("Couldn't bind to port 8080. Exiting.");
return 1;
}
// 设置回调函数,处理请求
evhttp_set_gencb(http, request_handler, NULL);
// 启动事件循环,开始监听和处理 HTTP 请求
event_base_dispatch(base);
// 释放资源
evhttp_free(http);
event_base_free(base);
return 0;
}
也可以用libevent编写HTTP的客户端程序,以下是示例:
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <event2/http_struct.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 回调函数:接收响应内容
void http_request_done(struct evhttp_request *req, void *arg) {
if (!req) {
fprintf(stderr, "Request failed: %s\n", evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
return;
}
int response_code = evhttp_request_get_response_code(req);
printf("Response Code: %d\n", response_code);
struct evbuffer *input_buffer = evhttp_request_get_input_buffer(req);
size_t len = evbuffer_get_length(input_buffer);
printf("Response Length: %zu bytes\n", len);
// 读取响应内容
char *data = (char*) malloc(len + 1);
evbuffer_copyout(input_buffer, data, len);
data[len] = '\0';
printf("Response Body:\n%s\n", data);
free(data);
}
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <hostname> <port>\n", argv[0]);
return 1;
}
const char *hostname = argv[1];
int port = atoi(argv[2]);
// 初始化 Libevent 和 event_base
struct event_base *base = event_base_new();
if (!base) {
fprintf(stderr, "Couldn't create event_base.\n");
return 1;
}
// 创建 HTTP 连接对象
struct evhttp_connection *conn = evhttp_connection_base_new(base, NULL, hostname, port);
if (!conn) {
fprintf(stderr, "Couldn't create evhttp_connection.\n");
event_base_free(base);
return 1;
}
// 创建 HTTP 请求对象
struct evhttp_request *req = evhttp_request_new(http_request_done, NULL);
if (!req) {
fprintf(stderr, "Couldn't create evhttp_request.\n");
evhttp_connection_free(conn);
event_base_free(base);
return 1;
}
// 设置请求的 URI 和参数
const char *uri = "/"; // 请求的路径
evhttp_add_header(evhttp_request_get_output_headers(req), "Host", hostname);
// 发起 GET 请求
if (evhttp_make_request(conn, req, EVHTTP_REQ_GET, uri) != 0) {
fprintf(stderr, "Couldn't make HTTP request.\n");
evhttp_request_free(req);
evhttp_connection_free(conn);
event_base_free(base);
return 1;
}
// 启动事件循环,等待响应
event_base_dispatch(base);
// 释放资源
evhttp_connection_free(conn);
event_base_free(base);
return 0;
}
3 源码剖析
3.1 evhttp_new
首先通过evhttp_new创建一个HTTP服务器实例:
static struct evhttp*
evhttp_new_object(void)
{
struct evhttp *http = NULL;
if ((http = mm_calloc(1, sizeof(struct evhttp))) == NULL) {
event_warn("%s: calloc", __func__);
return (NULL);
}
evutil_timerclear(&http->timeout);
evhttp_set_max_headers_size(http, EV_SIZE_MAX);
evhttp_set_max_body_size(http, EV_SIZE_MAX);
evhttp_set_default_content_type(http, "text/html; charset=ISO-8859-1");
evhttp_set_allowed_methods(http,
EVHTTP_REQ_GET |
EVHTTP_REQ_POST |
EVHTTP_REQ_HEAD |
EVHTTP_REQ_PUT |
EVHTTP_REQ_DELETE);
TAILQ_INIT(&http->sockets);
TAILQ_INIT(&http->callbacks);
TAILQ_INIT(&http->connections);
TAILQ_INIT(&http->virtualhosts);
TAILQ_INIT(&http->aliases);
return (http);
}
struct evhttp *
evhttp_new(struct event_base *base)
{
struct evhttp *http = NULL;
http = evhttp_new_object();
if (http == NULL)
return (NULL);
http->base = base;
return (http);
}
3.2 evhttp_bind_socket
在创建了HTTP服务器实例之后,便可以用evhttp_bind_socket来创建一个acceptor来接受client的请求了:
int
evhttp_bind_socket(struct evhttp *http, const char *address, ev_uint16_t port)
{
struct evhttp_bound_socket *bound =
evhttp_bind_socket_with_handle(http, address, port);
if (bound == NULL)
return (-1);
return (0);
}
3.3 evhttp_bind_socket_with_handle
evhttp_bind_socket只是对evhttp_bind_socket_with_handle的封装,展开evhttp_bind_socket_with_handle,看其实现:
struct evhttp_bound_socket *
evhttp_bind_socket_with_handle(struct evhttp *http, const char *address, ev_uint16_t port)
{
evutil_socket_t fd;
struct evhttp_bound_socket *bound;
// socket() => bind()
if ((fd = bind_socket(address, port, 1 /*reuse*/)) == -1)
return (NULL);
// listen()
if (listen(fd, 128) == -1) {
event_sock_warn(fd, "%s: listen", __func__);
evutil_closesocket(fd);
return (NULL);
}
// 用evconnlistener_new创建1个evconnlistener来接受client的connect请求
bound = evhttp_accept_socket_with_handle(http, fd);
if (bound != NULL) {
event_debug(("Bound to port %d - Awaiting connections ... ",
port));
return (bound);
}
return (NULL);
}
3.4 evhttp_accept_socket_with_handle
向evconnlistener注册回调,以便在evconnlistener成功accept客户端的connect请求后,通知user侧:
/* Listener callback when a connection arrives at a server. */
// 当1个client的connect请求到达时回调
static void
accept_socket_cb(struct evconnlistener *listener, evutil_socket_t nfd, struct sockaddr *peer_sa, int peer_socklen, void *arg)
{
struct evhttp *http = arg;
evhttp_get_request(http, nfd, peer_sa, peer_socklen);
}
// 用evconnlistener_new创建1个evconnlistener实例来accept客户端connect请求
struct evhttp_bound_socket *
evhttp_accept_socket_with_handle(struct evhttp *http, evutil_socket_t fd)
{
struct evhttp_bound_socket *bound;
struct evconnlistener *listener;
const int flags =
LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_EXEC|LEV_OPT_CLOSE_ON_FREE;
listener = evconnlistener_new(http->base, NULL, NULL,
flags,
0, /* Backlog is '0' because we already said 'listen' */
fd);
if (!listener)
return (NULL);
bound = evhttp_bind_listener(http, listener);
if (!bound) {
evconnlistener_free(listener);
return (NULL);
}
return (bound);
}
struct evhttp_bound_socket *
evhttp_bind_listener(struct evhttp *http, struct evconnlistener *listener)
{
struct evhttp_bound_socket *bound;
bound = mm_malloc(sizeof(struct evhttp_bound_socket));
if (bound == NULL)
return (NULL);
bound->listener = listener;
TAILQ_INSERT_TAIL(&http->sockets, bound, next);
// 设置accept成功的回调
evconnlistener_set_cb(listener, accept_socket_cb, http);
return bound;
}
3.5 evhttp_get_request
在展开evhttp_get_request看其实现:
static void
evhttp_get_request(struct evhttp *http, evutil_socket_t fd,
struct sockaddr *sa, ev_socklen_t salen)
{
struct evhttp_connection *evcon;
evcon = evhttp_get_request_connection(http, fd, sa, salen);
if (evcon == NULL) {
event_sock_warn(fd, "%s: cannot get connection on "EV_SOCK_FMT,
__func__, EV_SOCK_ARG(fd));
evutil_closesocket(fd);
return;
}
/* the timeout can be used by the server to close idle connections */
if (evutil_timerisset(&http->timeout))
evhttp_connection_set_timeout_tv(evcon, &http->timeout);
/*
* if we want to accept more than one request on a connection,
* we need to know which http server it belongs to.
*/
evcon->http_server = http;
TAILQ_INSERT_TAIL(&http->connections, evcon, next);
if (evhttp_associate_new_request_with_connection(evcon) == -1)
evhttp_connection_free(evcon);
}
3.6 evhttp_handle_request
最后通过evhttp_handle_request函数通知user侧:
// 在此通知user侧
static void
evhttp_handle_request(struct evhttp_request *req, void *arg)
{
struct evhttp *http = arg;
struct evhttp_cb *cb = NULL;
const char *hostname;
/* we have a new request on which the user needs to take action */
req->userdone = 0;
if (req->type == 0 || req->uri == NULL) {
evhttp_send_error(req, req->response_code, NULL);
return;
}
if ((http->allowed_methods & req->type) == 0) {
event_debug(("Rejecting disallowed method %x (allowed: %x)\n",
(unsigned)req->type, (unsigned)http->allowed_methods));
evhttp_send_error(req, HTTP_NOTIMPLEMENTED, NULL);
return;
}
/* handle potential virtual hosts */
hostname = evhttp_request_get_host(req);
if (hostname != NULL) {
evhttp_find_vhost(http, &http, hostname);
}
if ((cb = evhttp_dispatch_callback(&http->callbacks, req)) != NULL) {
(*cb->cb)(req, cb->cbarg);
return;
}
/* Generic call back */
if (http->gencb) {
// 在此通知user侧http请求到达
(*http->gencb)(req, http->gencbarg);
return;
} else {
/* We need to send a 404 here */
#define ERR_FORMAT "<html><head>" \
"<title>404 Not Found</title>" \
"</head><body>" \
"<h1>Not Found</h1>" \
"<p>The requested URL %s was not found on this server.</p>"\
"</body></html>\n"
char *escaped_html;
struct evbuffer *buf;
if ((escaped_html = evhttp_htmlescape(req->uri)) == NULL) {
evhttp_connection_free(req->evcon);
return;
}
if ((buf = evbuffer_new()) == NULL) {
mm_free(escaped_html);
evhttp_connection_free(req->evcon);
return;
}
evhttp_response_code_(req, HTTP_NOTFOUND, "Not Found");
evbuffer_add_printf(buf, ERR_FORMAT, escaped_html);
mm_free(escaped_html);
evhttp_send_page_(req, buf);
evbuffer_free(buf);
#undef ERR_FORMAT
}
}
static int
evhttp_associate_new_request_with_connection(struct evhttp_connection *evcon)
{
struct evhttp *http = evcon->http_server;
struct evhttp_request *req;
// 分配一个evhttp_request实例,表示此evhttp_connection上的1个http请求
if ((req = evhttp_request_new(evhttp_handle_request, http)) == NULL)
return (-1);
if ((req->remote_host = mm_strdup(evcon->address)) == NULL) {
event_warn("%s: strdup", __func__);
evhttp_request_free(req);
return (-1);
}
req->remote_port = evcon->port;
req->evcon = evcon; /* the request ends up owning the connection */
req->flags |= EVHTTP_REQ_OWN_CONNECTION;
/* We did not present the request to the user user yet, so treat it as
* if the user was done with the request. This allows us to free the
* request on a persistent connection if the client drops it without
* sending a request.
*/
req->userdone = 1;
TAILQ_INSERT_TAIL(&evcon->requests, req, next);
req->kind = EVHTTP_REQUEST;
// 关注读事件
evhttp_start_read_(evcon);
return (0);
}
3.7 evhttp_set_gencb
最后来看下是如何给HTTP服务器注册请求回调的,很简单:
// 给HTTP服务器注册一个回调,当client请求达到时会触发回调
void
evhttp_set_gencb(struct evhttp *http,
void (*cb)(struct evhttp_request *, void *), void *cbarg)
{
http->gencb = cb;
http->gencbarg = cbarg;
}
如此,从evhttp_new创建HTTP实例,到evhttp_bind_socket,再到evhttp_set_gencb几个api便可实现一个简易的HTTP服务器了。
3.8 evhttp_connection_set_closecb
// http断连时回调
void
evhttp_connection_set_closecb(struct evhttp_connection *evcon,
void (*cb)(struct evhttp_connection *, void *), void *cbarg)
{
evcon->closecb = cb;
evcon->closecb_arg = cbarg;
}
3.9 evhttp_request_set_on_complete_cb
// 请求完成时回调,可在此回调里做清理工作
void
evhttp_request_set_on_complete_cb(struct evhttp_request *req,
void (*cb)(struct evhttp_request *, void *), void *cb_arg)
{
req->on_complete_cb = cb;
req->on_complete_cb_arg = cb_arg;
}
此2接口在利用libevent编译HTTP程序时,经常会用到,所以本文特意加以介绍。此2回调主要是用来做清理工作的。
4 小结
4.1 优点
- 异步处理:libevent 的 HTTP 框架通过事件驱动的方式实现异步处理,能够处理高并发的请求,而不会阻塞在单个请求上。
- 轻量级:相比于其他 HTTP 服务器库,libevent 的 HTTP 框架更加轻量,适合在资源有限的环境中使用。
- 灵活性:用户可以通过设置自定义回调函数来处理特定路径的请求,灵活地扩展 HTTP 服务器功能。
4.2 缺点
- 不支持复杂的 HTTP 功能:libevent 的 HTTP 框架比较基础,缺少对一些高级 HTTP 特性的支持(如 WebSocket、HTTP/2 、HTTP/1.1流式传输等)。
- 错误处理简单:libevent 的 HTTP 框架对 HTTP 协议的错误处理相对简单,可能不适合需要处理复杂 HTTP 协议的场景。