c网络库libevent的http常用函数的使用(附带源码)
Libevent HTTP 核心函数详解与实战
- 核心概念
- HTTP 服务器端常用函数
- 1. 初始化与绑定
- 2. 设置请求处理回调
- 3. 在回调函数中处理请求
- 4. 发送响应
- 5. 启动与停止
- 6. 清理资源
- HTTP 客户端常用函数
- 1. 初始化
- 2. 创建连接
- 3. 创建并发送请求
- 4. 在回调函数中处理响应
- 5. 启动事件循环与清理
- 测试用例
- 编译命令
- 编译服务器
- 编译客户端
- 或者如果你的 libevent 安装将所有库合并了:
- *源码*
Libevent 是一个高性能的事件通知库,它封装了不同操作系统的 I/O 多路复用技术(如 epoll, kqueue, select, poll),提供了统一的事件处理接口。除了核心的事件处理,libevent 还提供了一些实用的上层协议实现,其中 HTTP 模块 (
event_http.h
) 是非常常用和强大的一个,可以方便地构建高性能的 HTTP 服务器和客户端。
本文将深入探讨 libevent HTTP 模块中一些最常用的函数,并通过具体的 C 语言测试用例来演示它们的使用方法。
也是为我下面的服务器做铺垫
20250428_080643
核心概念
在深入函数之前,先了解几个 libevent HTTP 模块的核心结构体:
struct event_base
: 事件循环(Event Loop)的基石,所有事件(I/O、定时器、信号等)都在一个event_base
上注册和分发。struct evhttp
: 代表一个 HTTP 服务器实例。它监听指定的地址和端口,接收并处理传入的 HTTP 请求。struct evhttp_connection
: 代表一个 HTTP 连接。对于服务器端,它通常代表一个已接受的客户端连接;对于客户端,它代表一个到目标服务器的连接。struct evhttp_request
: 代表一个 HTTP 请求。在服务器端,它封装了接收到的客户端请求信息(方法、URI、头部、请求体等);在客户端,它封装了要发送到服务器的请求信息以及接收到的响应信息。- 回调函数 (Callback): libevent 的核心是事件驱动,HTTP 模块也不例外。你需要定义回调函数来处理特定的事件,例如:收到新请求、收到响应数据、连接关闭等。
HTTP 服务器端常用函数
构建一个 HTTP 服务器通常涉及以下步骤和函数:
1. 初始化与绑定
-
struct event_base *event_base_new(void)
:- 功能:创建一个新的
event_base
实例,作为事件循环的基础。 - 返回:成功时返回
event_base
指针,失败时返回NULL
。
- 功能:创建一个新的
-
struct evhttp *evhttp_new(struct event_base *base)
:- 功能:创建一个新的
evhttp
服务器实例,并将其关联到一个event_base
。 - 参数
base
: 指向event_base
的指针。 - 返回:成功时返回
evhttp
指针,失败时返回NULL
。
- 功能:创建一个新的
-
int evhttp_bind_socket(struct evhttp *http, const char *address, ev_uint16_t port)
: (旧版接口,新版推荐evhttp_bind_socket_with_handle
)- 功能:让
evhttp
服务器监听指定的 IP 地址和端口。 - 参数
http
:evhttp
服务器实例。 - 参数
address
: 要监听的 IP 地址(如 “0.0.0.0” 表示监听所有接口)。 - 参数
port
: 要监听的端口号。 - 返回:成功时返回 0,失败时返回 -1。
- 功能:让
-
struct evhttp_bound_socket *evhttp_bind_socket_with_handle(struct evhttp *http, const char *address, ev_uint16_t port)
:- 功能:同
evhttp_bind_socket
,但返回一个句柄,可以用于后续操作(如获取实际监听的端口)。这是推荐使用的新接口。 - 返回:成功时返回
evhttp_bound_socket
句柄,失败时返回NULL
。
- 功能:同
2. 设置请求处理回调
当服务器收到一个 HTTP 请求时,需要有函数来处理它。libevent 提供了两种主要的回调设置方式:
-
void evhttp_set_gencb(struct evhttp *http, void (*cb)(struct evhttp_request *, void *), void *arg)
:- 功能:设置一个“通用回调函数”(Generic Callback)。如果没有任何其他更具体的回调函数匹配请求的 URI,则会调用此通用回调。
- 参数
http
:evhttp
服务器实例。 - 参数
cb
: 回调函数指针。该函数接收evhttp_request
和一个用户提供的参数arg
。 - 参数
arg
: 传递给回调函数的额外用户数据。
-
int evhttp_set_cb(struct evhttp *http, const char *path, void (*cb)(struct evhttp_request *, void *), void *arg)
:- 功能:为特定的 URI 路径设置回调函数。当请求的 URI 前缀匹配
path
时,将调用此回调。 - 参数
http
:evhttp
服务器实例。 - 参数
path
: 要匹配的 URI 路径(如 “/hello”, “/api/users”)。 - 参数
cb
: 处理该路径请求的回调函数。 - 参数
arg
: 传递给回调函数的额外用户数据。 - 返回:成功时返回 0,失败时返回 -1。
注意:
evhttp_set_cb
的匹配优先级高于evhttp_set_gencb
。 - 功能:为特定的 URI 路径设置回调函数。当请求的 URI 前缀匹配
3. 在回调函数中处理请求
回调函数 void (*cb)(struct evhttp_request *req, void *arg)
是处理逻辑的核心。struct evhttp_request *req
参数包含了所有请求相关的信息。常用函数有:
-
const char *evhttp_request_get_uri(const struct evhttp_request *req)
:- 功能:获取请求的完整 URI (包括查询参数)。
- 返回:指向 URI 字符串的指针(只读)。
-
enum evhttp_cmd_type evhttp_request_get_command(const struct evhttp_request *req)
:- 功能:获取请求的 HTTP 方法(GET, POST, PUT, DELETE 等)。
- 返回:枚举类型
evhttp_cmd_type
的值 (如EVHTTP_REQ_GET
,EVHTTP_REQ_POST
)。
-
struct evkeyvalq *evhttp_request_get_input_headers(struct evhttp_request *req)
:- 功能:获取请求的 HTTP 头部信息。这是一个键值对队列。
- 返回:指向
evkeyvalq
结构体的指针。
-
const char *evhttp_find_header(const struct evkeyvalq *headers, const char *key)
:- 功能:在给定的头部信息中查找指定键(Header Name)的值。注意键是大小写不敏感的。
- 参数
headers
: 通常是evhttp_request_get_input_headers()
的返回值。 - 参数
key
: 要查找的头部名称 (如 “Content-Type”, “User-Agent”)。 - 返回:找到则返回头部值的字符串指针(只读),否则返回
NULL
。
-
struct evbuffer *evhttp_request_get_input_buffer(struct evhttp_request *req)
:- 功能:获取请求体(Request Body)的数据。数据存储在
evbuffer
中。 - 返回:指向
evbuffer
的指针。你可以使用evbuffer
相关的函数(如evbuffer_get_length
,evbuffer_pullup
,evbuffer_remove
) 来读取数据。
- 功能:获取请求体(Request Body)的数据。数据存储在
-
void evhttp_parse_query(const char *uri, struct evkeyvalq *headers)
: (旧版,新版推荐evhttp_parse_query_str
)- 功能:解析 URI 中的查询字符串 (query string,
?
之后的部分),并将解析出的键值对添加到headers
中。 - 参数
uri
: 通常是evhttp_request_get_uri()
的返回值。 - 参数
headers
: 用于存储解析结果的evkeyvalq
结构体(通常需要先evhttp_request_get_uri_parts
或手动初始化)。
- 功能:解析 URI 中的查询字符串 (query string,
-
int evhttp_parse_query_str(const char *query_string, struct evkeyvalq *params)
:- 功能:解析查询字符串,并将键值对存入
params
。 - 参数
query_string
: 查询字符串本身(不包含?
)。 - 参数
params
: 存储解析结果的evkeyvalq
。 - 返回:成功时返回 0。
- 功能:解析查询字符串,并将键值对存入
-
const char *evhttp_request_get_host(struct evhttp_request *req)
:- 功能:获取请求的 Host 头部信息。
- 返回:Host 字符串指针,或
NULL
。
4. 发送响应
处理完请求后,需要向客户端发送响应。
-
struct evkeyvalq *evhttp_request_get_output_headers(struct evhttp_request *req)
:- 功能:获取用于添加响应头部的
evkeyvalq
结构。 - 返回:指向
evkeyvalq
的指针。
- 功能:获取用于添加响应头部的
-
int evhttp_add_header(struct evkeyvalq *headers, const char *key, const char *value)
:- 功能:向头部队列中添加一个键值对。
- 参数
headers
: 通常是evhttp_request_get_output_headers()
的返回值。 - 参数
key
: 响应头部名称 (如 “Content-Type”, “Server”)。 - 参数
value
: 响应头部的值。 - 返回:成功时返回 0,失败时返回 -1。
-
void evhttp_send_reply(struct evhttp_request *req, int code, const char *reason, struct evbuffer *databuf)
:- 功能:发送一个完整的 HTTP 响应。这是最常用的发送响应函数。
- 参数
req
: 当前处理的请求对象。 - 参数
code
: HTTP 状态码 (如 200, 404, 500)。 - 参数
reason
: 状态码对应的原因短语 (如 “OK”, “Not Found”)。如果为NULL
,libevent 会尝试根据code
自动填充。 - 参数
databuf
: 包含响应体的evbuffer
。如果响应没有 body,可以传入NULL
。libevent 会负责发送Content-Length
头部(除非你显式设置了Transfer-Encoding: chunked
)。发送后,databuf
中的数据会被消耗掉。
-
void evhttp_send_error(struct evhttp_request *req, int error, const char *reason)
:- 功能:发送一个简单的错误响应。通常会生成一个包含错误信息的 HTML 页面作为响应体。
- 参数
req
: 当前处理的请求对象。 - 参数
error
: HTTP 错误状态码 (如 400, 404, 500)。 - 参数
reason
: 原因短语。如果为NULL
或空字符串,会使用默认的原因短语。
-
Chunked 响应 (适用于大响应或流式响应):
void evhttp_send_reply_start(struct evhttp_request *req, int code, const char *reason)
: 发送响应头和状态行,表明后续将使用 Chunked 编码。会自动添加Transfer-Encoding: chunked
头部。void evhttp_send_reply_chunk(struct evhttp_request *req, struct evbuffer *databuf)
: 发送一个数据块 (chunk)。databuf
中的数据会被发送。void evhttp_send_reply_end(struct evhttp_request *req)
: 发送最后一个大小为 0 的 chunk,标志着响应结束。
5. 启动与停止
-
int event_base_dispatch(struct event_base *base)
:- 功能:启动
event_base
的事件循环。此函数会阻塞,直到循环被显式停止或没有活动事件。 - 返回:成功时返回 0,失败时返回 -1,如果因为没有活动事件而退出则返回 1。
- 功能:启动
-
int event_base_loopbreak(struct event_base *base)
:- 功能:使正在运行的
event_base_dispatch
或event_base_loop
在处理完当前事件后立即退出。 - 返回:成功时返回 0,失败时返回 -1。
- 功能:使正在运行的
-
int event_base_loopexit(struct event_base *base, const struct timeval *tv)
:- 功能:使事件循环在指定时间
tv
后或处理完当前事件后(如果tv
为NULL
)退出。 - 返回:成功时返回 0,失败时返回 -1。
- 功能:使事件循环在指定时间
6. 清理资源
void evhttp_free(struct evhttp *http)
:- 功能:释放
evhttp
服务器实例及其占用的资源(包括绑定的套接字句柄)。
- 功能:释放
void event_base_free(struct event_base *base)
:- 功能:释放
event_base
及其相关资源。
- 功能:释放
HTTP 客户端常用函数
使用 libevent 构建 HTTP 客户端也相对直接:
1. 初始化
- 同样需要
event_base_new()
来创建事件循环。
2. 创建连接
struct evhttp_connection *evhttp_connection_base_new(struct event_base *base, struct evdns_base *dnsbase, const char *address, ev_uint16_t port)
:- 功能:创建一个到目标 HTTP 服务器的连接。
- 参数
base
: 事件循环event_base
。 - 参数
dnsbase
: DNS 解析器。如果为NULL
,会使用阻塞的getaddrinfo
。为了实现完全异步,应使用evdns_base_new()
创建一个evdns_base
。 - 参数
address
: 目标服务器的 IP 地址或主机名。 - 参数
port
: 目标服务器的端口号。 - 返回:成功时返回
evhttp_connection
指针,失败时返回NULL
。
3. 创建并发送请求
-
struct evhttp_request *evhttp_request_new(void (*cb)(struct evhttp_request *, void *), void *arg)
:- 功能:创建一个新的
evhttp_request
对象,用于发起客户端请求。 - 参数
cb
: 请求完成(收到响应或发生错误)时的回调函数。 - 参数
arg
: 传递给回调函数的用户数据。 - 返回:成功时返回
evhttp_request
指针,失败时返回NULL
。
- 功能:创建一个新的
-
struct evkeyvalq *evhttp_request_get_output_headers(struct evhttp_request *req)
:- 功能:获取用于添加请求头部的
evkeyvalq
结构。 - 返回:指向
evkeyvalq
的指针。(与服务器端发送响应时使用的函数相同)
- 功能:获取用于添加请求头部的
-
int evhttp_add_header(struct evkeyvalq *headers, const char *key, const char *value)
:- 功能:向请求头部添加键值对。(与服务器端相同)
- 注意:通常需要手动添加
Host
头部。
-
struct evbuffer *evhttp_request_get_output_buffer(struct evhttp_request *req)
:- 功能:获取用于添加请求体的
evbuffer
。 - 返回:指向
evbuffer
的指针。可以使用evbuffer_add*
系列函数向其中添加数据。
- 功能:获取用于添加请求体的
-
int evhttp_make_request(struct evhttp_connection *evcon, struct evhttp_request *req, enum evhttp_cmd_type type, const char *uri)
:- 功能:通过指定的连接
evcon
发送req
对象所描述的 HTTP 请求。 - 参数
evcon
:evhttp_connection_base_new()
创建的连接。 - 参数
req
:evhttp_request_new()
创建并配置好的请求对象。 - 参数
type
: HTTP 请求方法 (如EVHTTP_REQ_GET
,EVHTTP_REQ_POST
)。 - 参数
uri
: 请求的路径和查询字符串 (如 “/index.html”, “/search?q=libevent”)。 - 返回:成功时返回 0,失败时返回 -1。请求是异步发送的,结果将在
evhttp_request_new
指定的回调中处理。
- 功能:通过指定的连接
4. 在回调函数中处理响应
客户端请求的回调函数 void (*cb)(struct evhttp_request *req, void *arg)
在收到响应或发生错误时被调用。
-
检查请求状态:
- 在回调函数中,首先应检查
req
是否为NULL
。如果为NULL
,表示发生了严重错误(如连接失败)。 - 如果不为
NULL
,检查req->response_code
来获取 HTTP 状态码。
- 在回调函数中,首先应检查
-
int evhttp_request_get_response_code(const struct evhttp_request *req)
:- 功能:获取服务器响应的 HTTP 状态码。
- 返回:HTTP 状态码。
-
struct evkeyvalq *evhttp_request_get_input_headers(struct evhttp_request *req)
:- 功能:获取响应的 HTTP 头部。(与服务器端获取请求头相同)
-
struct evbuffer *evhttp_request_get_input_buffer(struct evhttp_request *req)
:- 功能:获取响应体数据。(与服务器端获取请求体相同)
5. 启动事件循环与清理
- 同样需要
event_base_dispatch()
来运行事件循环以发送请求和接收响应。 void evhttp_connection_free(struct evhttp_connection *evcon)
:- 功能:释放客户端连接对象。如果连接上有未完成的请求,它们会被取消。
void evhttp_request_free(struct evhttp_request *req)
:- 功能:释放客户端请求对象。注意:这个函数通常不需要手动调用,因为在请求完成的回调被调用后,libevent 内部通常会处理
req
的释放(除非你在回调中通过特定方式阻止了自动释放)。但在某些错误路径或特殊场景下可能需要。
- 功能:释放客户端请求对象。注意:这个函数通常不需要手动调用,因为在请求完成的回调被调用后,libevent 内部通常会处理
- 最后别忘了
event_base_free()
。
测试用例
下面提供一个简单的 HTTP 服务器和客户端的测试用例。
编译命令
假设你的源文件名为 http_server.c
和 http_client.c
,并且已经安装了 libevent 开发库(通常包名为 libevent-dev
或 libevent-devel
)。
编译服务器
gcc http_server.c -o http_server -levent -Wall -g
编译客户端
gcc http_client.c -o http_client -levent -levent_core -levent_extra -Wall -g
或者如果你的 libevent 安装将所有库合并了:
源码
- HTTP 服务器 (http_server.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <event2/keyvalq_struct.h>// 通用请求处理回调函数
void generic_handler(struct evhttp_request *req, void *arg) {const char *uri = evhttp_request_get_uri(req);enum evhttp_cmd_type method = evhttp_request_get_command(req);struct evkeyvalq *headers = evhttp_request_get_input_headers(req);struct evbuffer *req_body = evhttp_request_get_input_buffer(req);size_t body_len = evbuffer_get_length(req_body);printf("Received a request:\n");printf(" URI: %s\n", uri);printf(" Method: %s\n", method == EVHTTP_REQ_GET ? "GET" :method == EVHTTP_REQ_POST ? "POST" : "Other");printf(" Headers:\n");struct evkeyval *header;for (header = headers->tqh_first; header; header = header->next.tqe_next) {printf(" %s: %s\n", header->key, header->value);}if (body_len > 0) {printf(" Body (len %zu):\n", body_len);// 为了演示,只打印前 1024 字节char body_data[1025];size_t copy_len = body_len > 1024 ? 1024 : body_len;memcpy(body_data, evbuffer_pullup(req_body, copy_len), copy_len);body_data[copy_len] = '\0';printf(" %s\n", body_data);} else {printf(" Body: (empty)\n");}printf("--------------------\n");// 创建响应内容struct evbuffer *buf = evbuffer_new();if (!buf) {fprintf(stderr, "Failed to create response buffer\n");evhttp_send_error(req, HTTP_INTERNAL, "Internal Server Error");return;}evbuffer_add_printf(buf, "<html><body><h1>Hello from libevent!</h1>");evbuffer_add_printf(buf, "<p>You requested: %s</p>", uri);if (method == EVHTTP_REQ_POST && body_len > 0) {evbuffer_add_printf(buf, "<p>Received POST data (first %zu bytes):</p><pre>", body_len);evbuffer_add_reference(buf, evbuffer_pullup(req_body, -1), body_len, NULL, NULL); // More efficient for larger bodiesevbuffer_add_printf(buf, "</pre>");}evbuffer_add_printf(buf, "</body></html>");// 添加响应头部evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "text/html; charset=UTF-8");evhttp_add_header(evhttp_request_get_output_headers(req), "Server", "Libevent Test Server");// 发送响应evhttp_send_reply(req, HTTP_OK, "OK", buf);// 释放响应 bufferevbuffer_free(buf);
}// 特定路径的回调
void specific_handler(struct evhttp_request *req, void *arg) {struct evbuffer *buf = evbuffer_new();evbuffer_add_printf(buf, "<html><body><h2>This is the specific /hello path!</h2></body></html>");evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "text/html");evhttp_send_reply(req, HTTP_OK, "OK", buf);evbuffer_free(buf);printf("Handled /hello request.\n");
}// SIGINT 信号处理函数,用于优雅退出
void signal_handler(evutil_socket_t fd, short event, void *arg) {struct event_base *base = (struct event_base *)arg;printf("Caught signal, exiting cleanly...\n");event_base_loopbreak(base); // 停止事件循环
}int main(int argc, char **argv) {struct event_base *base;struct evhttp *http;struct evhttp_bound_socket *handle;struct event *sigint_event;ev_uint16_t port = 8088;const char *address = "0.0.0.0";// 初始化 libeventbase = event_base_new();if (!base) {fprintf(stderr, "Could not initialize libevent!\n");return 1;}// 创建 HTTP 服务器http = evhttp_new(base);if (!http) {fprintf(stderr, "Could not create evhttp instance!\n");event_base_free(base);return 1;}// 设置回调函数// 设置特定路径的回调evhttp_set_cb(http, "/hello", specific_handler, NULL);// 设置通用回调(处理所有其他请求)evhttp_set_gencb(http, generic_handler, NULL);// 绑定端口handle = evhttp_bind_socket_with_handle(http, address, port);if (!handle) {fprintf(stderr, "Could not bind to %s:%d!\n", address, port);evhttp_free(http);event_base_free(base);return 1;}// 获取实际绑定的端口(如果传入的 port 为 0)struct sockaddr_storage ss;evutil_socket_t fd = evhttp_bound_socket_get_fd(handle);ev_socklen_t len = sizeof(ss);if (getsockname(fd, (struct sockaddr *)&ss, &len) == 0) {if (ss.ss_family == AF_INET) {port = ntohs(((struct sockaddr_in*)&ss)->sin_port);} else if (ss.ss_family == AF_INET6) {port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port);}}printf("HTTP server listening on %s:%d\n", address, port);// 设置 SIGINT 信号处理器以实现优雅退出sigint_event = evsignal_new(base, SIGINT, signal_handler, base);if (!sigint_event || event_add(sigint_event, NULL) < 0) {fprintf(stderr, "Could not create/add SIGINT event!\n");}// 启动事件循环event_base_dispatch(base);// 清理printf("Cleaning up...\n");if (sigint_event) event_free(sigint_event);evhttp_free(http); // evhttp_free 会关闭 handleevent_base_free(base);printf("Server stopped.\n");return 0;
}
- HTTP 客户端 (http_client.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <event2/keyvalq_struct.h>
#include <event2/dns.h> // For evdns_base if needed// 请求完成回调函数
void http_request_done(struct evhttp_request *req, void *arg) {struct event_base *base = (struct event_base *)arg;char buffer[256];int nread;if (!req) {fprintf(stderr, "Request failed: Connection error or timeout.\n");} else if (evhttp_request_get_response_code(req) == 0) {// 这通常意味着连接在收到完整响应头之前关闭fprintf(stderr, "Request failed: Connection closed prematurely (response code 0).\n");// 尝试获取错误信息int errcode = EVUTIL_SOCKET_ERROR();fprintf(stderr, " Socket error: %s (%d)\n", evutil_socket_error_to_string(errcode), errcode);} else {printf("Received response:\n");printf(" Status: %d %s\n", evhttp_request_get_response_code(req), req->response_code_line ? req->response_code_line : "");printf(" Headers:\n");struct evkeyvalq *headers = evhttp_request_get_input_headers(req);struct evkeyval *header;for (header = headers->tqh_first; header; header = header->next.tqe_next) {printf(" %s: %s\n", header->key, header->value);}printf(" Body:\n");struct evbuffer *buf = evhttp_request_get_input_buffer(req);while ((nread = evbuffer_remove(buf, buffer, sizeof(buffer) - 1)) > 0) {buffer[nread] = '\0';printf("%s", buffer); // 直接打印,不加换行}printf("\n--------------------\n"); // 在整个 body 后加换行}// 无论成功失败,结束事件循环event_base_loopexit(base, NULL);
}int main(int argc, char **argv) {struct event_base *base;struct evhttp_connection *conn = NULL;struct evhttp_request *req = NULL;struct evdns_base *dns_base = NULL; // 可选,用于异步 DNSconst char *server_address = "127.0.0.1";ev_uint16_t server_port = 8088;const char *request_uri = "/"; // 默认请求根路径enum evhttp_cmd_type method = EVHTTP_REQ_GET;const char *post_data = NULL;// 解析命令行参数 (简单示例)if (argc > 1) request_uri = argv[1];if (argc > 2 && strcmp(argv[2], "POST") == 0) {method = EVHTTP_REQ_POST;if (argc > 3) {post_data = argv[3];} else {post_data = "Default POST data from client";}}if (argc > 4) server_address = argv[4];if (argc > 5) server_port = (ev_uint16_t)atoi(argv[5]);// 初始化 libeventbase = event_base_new();if (!base) {fprintf(stderr, "Could not initialize libevent!\n");return 1;}// 可选:初始化异步 DNSdns_base = evdns_base_new(base, 1); // 1 表示使用系统默认配置if (!dns_base) {fprintf(stderr, "Warning: Could not create evdns_base, using blocking DNS.\n");}// 创建到服务器的连接conn = evhttp_connection_base_new(base, dns_base, server_address, server_port);if (!conn) {fprintf(stderr, "Could not create connection to %s:%d\n", server_address, server_port);goto cleanup;}// 设置连接超时 (可选)// evhttp_connection_set_timeout(conn, 5); // 5 seconds// 创建请求对象req = evhttp_request_new(http_request_done, base); // 将 base 作为参数传给回调if (!req) {fprintf(stderr, "Could not create request object\n");goto cleanup;}// 添加请求头部struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req);evhttp_add_header(output_headers, "Host", server_address); // 非常重要!evhttp_add_header(output_headers, "User-Agent", "Libevent Client Example/1.0");evhttp_add_header(output_headers, "Connection", "close"); // 短连接示例// 如果是 POST 请求,添加请求体和 Content-Typeif (method == EVHTTP_REQ_POST && post_data) {struct evbuffer *output_buffer = evhttp_request_get_output_buffer(req);evbuffer_add_printf(output_buffer, "%s", post_data);// 添加 Content-Length (libevent 会自动计算) 或 Content-Typechar len_str[20];snprintf(len_str, sizeof(len_str), "%zu", strlen(post_data));// evhttp_add_header(output_headers, "Content-Length", len_str); // 通常不需要手动加,libevent 会处理evhttp_add_header(output_headers, "Content-Type", "application/x-www-form-urlencoded"); // 或者其他类型}// 发起请求if (evhttp_make_request(conn, req, method, request_uri) != 0) {fprintf(stderr, "Could not make request\n");// 注意:如果 make_request 失败,req 可能需要手动释放,但这里为了简化,依赖 cleanupgoto cleanup;}req = NULL; // make_request 成功后,req 的生命周期由 libevent 管理printf("Making %s request to http://%s:%d%s\n",method == EVHTTP_REQ_GET ? "GET" : "POST",server_address, server_port, request_uri);if (post_data) {printf(" With POST data: %s\n", post_data);}// 启动事件循环,等待请求完成event_base_dispatch(base);cleanup:printf("Cleaning up client...\n");// req 在 make_request 成功后不应在此处释放,回调完成后 libevent 会处理// 如果 make_request 失败或从未调用,则需要释放: if (req) evhttp_request_free(req);if (conn) evhttp_connection_free(conn);if (dns_base) evdns_base_free(dns_base, 0); // 0表示不等待未完成的查询if (base) event_base_free(base);printf("Client finished.\n");return 0;
}