c网络库libevent的http常用函数的使用(附带源码)

Libevent HTTP 核心函数详解与实战

    • 核心概念
    • [HTTP 服务器端常用函数](#HTTP 服务器端常用函数)
      • [1. 初始化与绑定](#1. 初始化与绑定)
      • [2. 设置请求处理回调](#2. 设置请求处理回调)
      • [3. 在回调函数中处理请求](#3. 在回调函数中处理请求)
      • [4. 发送响应](#4. 发送响应)
      • [5. 启动与停止](#5. 启动与停止)
      • [6. 清理资源](#6. 清理资源)
    • [HTTP 客户端常用函数](#HTTP 客户端常用函数)
      • [1. 初始化](#1. 初始化)
      • [2. 创建连接](#2. 创建连接)
      • [3. 创建并发送请求](#3. 创建并发送请求)
      • [4. 在回调函数中处理响应](#4. 在回调函数中处理响应)
      • [5. 启动事件循环与清理](#5. 启动事件循环与清理)
    • 测试用例
  • 编译服务器
  • 编译客户端
  • [或者如果你的 libevent 安装将所有库合并了:](#或者如果你的 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)
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 bodies
         evbuffer_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);

    // 释放响应 buffer
    evbuffer_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";

    // 初始化 libevent
    base = 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 会关闭 handle
    event_base_free(base);

    printf("Server stopped.\n");
    return 0;
}
  1. HTTP 客户端 (http_client.c)
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; // 可选,用于异步 DNS

    const 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]);


    // 初始化 libevent
    base = event_base_new();
    if (!base) {
        fprintf(stderr, "Could not initialize libevent!\n");
        return 1;
    }

    // 可选:初始化异步 DNS
    dns_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-Type
    if (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-Type
        char 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 可能需要手动释放,但这里为了简化,依赖 cleanup
        goto 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;
}
相关推荐
YuforiaCode3 分钟前
第十六届蓝桥杯 2025 C/C++组 旗帜
c语言·c++·蓝桥杯
YuforiaCode5 分钟前
第十六届蓝桥杯 2025 C/C++B组 第二轮省赛 全部题解(未完结)
c语言·c++·蓝桥杯
lifewange1 小时前
jmeter-Beashell获取请求body data
网络协议·jmeter·http
keep intensify1 小时前
数据结构---单链表的增删查改
c语言·数据结构·c++·经验分享·学习·算法·分享
JhonKI2 小时前
【Linux网络】深入解析I/O多路转接 - Select
linux·运维·网络
精神病不行计算机不上班2 小时前
【计网】计算机网络的类别与性能
网络·计算机网络
地平线开发者2 小时前
C++ 部署的性能优化方法
c++·算法·自动驾驶
我学上瘾了2 小时前
链表反转_leedcodeP206
网络·redis·链表
Yingye Zhu(HPXXZYY)2 小时前
洛谷P12238 [蓝桥杯 2023 国 Java A] 单词分类
c++·算法·蓝桥杯
工藤新一¹3 小时前
C++/SDL 进阶游戏开发 —— 双人塔防(代号:村庄保卫战 16)
c++·游戏引擎·sdl·c++游戏开发·实践项目