当前位置: 首页 > news >正文

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 模块的核心结构体:

  1. struct event_base: 事件循环(Event Loop)的基石,所有事件(I/O、定时器、信号等)都在一个 event_base 上注册和分发。
  2. struct evhttp: 代表一个 HTTP 服务器实例。它监听指定的地址和端口,接收并处理传入的 HTTP 请求。
  3. struct evhttp_connection: 代表一个 HTTP 连接。对于服务器端,它通常代表一个已接受的客户端连接;对于客户端,它代表一个到目标服务器的连接。
  4. struct evhttp_request: 代表一个 HTTP 请求。在服务器端,它封装了接收到的客户端请求信息(方法、URI、头部、请求体等);在客户端,它封装了要发送到服务器的请求信息以及接收到的响应信息。
  5. 回调函数 (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

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) 来读取数据。
  • 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 或手动初始化)。
  • 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_dispatchevent_base_loop 在处理完当前事件后立即退出。
    • 返回:成功时返回 0,失败时返回 -1。
  • int event_base_loopexit(struct event_base *base, const struct timeval *tv):

    • 功能:使事件循环在指定时间 tv 后或处理完当前事件后(如果 tvNULL)退出。
    • 返回:成功时返回 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 的释放(除非你在回调中通过特定方式阻止了自动释放)。但在某些错误路径或特殊场景下可能需要。
  • 最后别忘了 event_base_free()

测试用例

下面提供一个简单的 HTTP 服务器和客户端的测试用例。

编译命令

假设你的源文件名为 http_server.chttp_client.c,并且已经安装了 libevent 开发库(通常包名为 libevent-devlibevent-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 安装将所有库合并了:

源码

  1. 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;
}
  1. 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;
}

相关文章:

  • 打造惊艳的渐变色下划线动画:CSS实现详解
  • Kotlin -> lateinit 和 lazy 详解
  • 聚焦智能体未来,领驭科技在微软创想未来峰会大放异彩
  • 按键精灵安卓ios辅助工具脚本:实用的文件插件(lua开源)
  • 私有知识库 Coco AI 实战(四):打造 ES 索引参数小助手
  • 前端漏洞不扫描理由
  • Linux systemd 从理论到实践:现代系统管理的核心工具
  • C++ 单例对象自动释放(保姆级讲解)
  • Hearts of Iron IV 钢铁雄心 4 [DLC 解锁] [Windows SteamOS macOS]
  • 机器学习-入门-决策树(1)
  • 第17节:传统分类模型-随机森林与决策树
  • day10 python机器学习全流程实践
  • Azure Synapse Dedicated SQL pool企业权限管理
  • 数据库操作
  • 轻松实现CI/CD: 用Go编写的命令行工具简化Jenkins构建
  • Java练习8
  • 【AlphaFold2】Feature extraction:提取特征,为模型输入做准备|Datapipeline讲解
  • 激光扫描仪的用途及优势
  • Java常用注解通俗解释
  • 【计算机视觉】目标检测:深度解析YOLOv5:下一代实时目标检测框架实战指南
  • 白玉兰奖征片综述丨国产剧集创作的此消彼长
  • “人工智能是年轻的事业,也是年轻人的事业”,沪上高校师生畅谈感想
  • 深圳宝安区一宗涉宅用地中止出让,起始总价86.27亿元
  • 财政部农业农村司司长吴奇修接受纪律审查和监察调查
  • 北京公园使用指南
  • 加拿大温哥华一车辆冲撞人群,造成多人伤亡