当前位置: 首页 > 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;
}

相关文章:

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

Libevent HTTP 核心函数详解与实战 核心概念HTTP 服务器端常用函数1. 初始化与绑定2. 设置请求处理回调3. 在回调函数中处理请求4. 发送响应5. 启动与停止6. 清理资源 HTTP 客户端常用函数1. 初始化2. 创建连接3. 创建并发送请求4. 在回调函数中处理响应5. 启动事件循环与清理 …...

java练习3

随机生成20个数字&#xff08;随机种子&#xff09; 分别使用冒泡排序、二叉树排序、插入排序进行排序 并输出最终结果以及三种排序使用的时间 package a01_第一次练习.a03_排序;import java.time.Duration; import java.time.LocalDateTime; import java.util.TreeSet;publi…...

当 AI 成为 “数字新物种”:人类职业的重构与进化

一、AI 的 “替代清单”&#xff1a;从流水线到办公室的全面侵袭 在深圳某智能工厂&#xff0c;机械臂正以 0.01 毫米的精度完成手机组装&#xff0c;100 台机器人 24 小时运转&#xff0c;替代了 3000 名工人。这种场景正在全球制造业蔓延 —— 麦肯锡预测&#xff0c;到 203…...

HarmonyOS ArkUI交互事件与手势处理全解析:从基础到高级实践

文章目录 一、交互事件1.1 通用事件1.1.1 事件分发1.1.1.1 触摸测试1. 触摸测试基本流程2. 触摸测试控制3. 自定义事件拦截4. 禁用控制5. 触摸热区设置6. 安全组件 1.1.1.2 事件响应链收集 1.1.2 触屏事件1.1.3 键鼠事件1.1.3.1 鼠标事件1.1.3.2 按键事件 1.1.4 焦点事件1.1.5 …...

【计算机网络】面试常考——GET 和 POST 的区别

GET 和 POST 的区别 GET 和 POST 是 HTTP 协议中最常用的两种请求方法&#xff0c;它们的主要区别体现在 用途、数据传输方式、安全性、缓存机制 等方面。以下是详细对比&#xff1a; 1. 用途 GET POST 主要用于 获取数据&#xff08;如查询、搜索&#xff09;。 主要用于 提…...

AI编程工具“幻觉”风险与飞算JavaAl的破局之道

近年来&#xff0c;AI编程辅助工具迅速崛起&#xff0c;极大地提升了开发者的工作效率。然而&#xff0c;这些工具普遍存在一个被称为“幻觉”(hallucination)的风险——AI可能会生成看似合理但实际错误、不安全或低效的代码。这种现象在复杂业务逻辑和特定领域开发中尤为明显&…...

【Python零基础入门系列】第1篇:Python 是什么?怎么装环境?推荐哪些 IDE?

各位网友们,欢迎来到我的 Python 学习专栏! 前两天看到新闻英伟达为 CUDA 添加原生 Python 支持,意味着开发者可直接用 Python 操作 GPU,加速 AI 和高性能计算,降低门槛,让 Python 的应用范围更广、能力更强。 一直想写一系列文章教知友们从零开始学会 Python 编程,目…...

VPN访问SAP组服务器报登陆负载均衡错误88:无法连接到消息服务器(RC=9)

用户反馈用SAPGUI接入SAP时报错&#xff1a;登陆负载均衡错误88&#xff1a;无法连接到消息服务器(RC9) 经了解是通过VPN访问&#xff0c;但VPN没有放行ICMP访问&#xff0c;导致不能PING通&#xff0c;不能确认是网络问题还是什么问题。 解决方案&#xff1a; 1、VPN由原&am…...

Linux查看程序端口占用情况

大家好&#xff0c;欢迎来到程序视点&#xff01;我是你们的老朋友.小二&#xff01; 核心问题&#xff1a; Tomcat 8080端口启动失败&#xff0c;提示端口被占用&#xff0c;但常规检查未发现Tomcat进程占用该端口。 关键排查步骤&#xff1a; 初步检查 使用 ps -aux | gre…...

[C]基础14.字符函数和字符串函数

博客主页&#xff1a;向不悔本篇专栏&#xff1a;[C]您的支持&#xff0c;是我的创作动力。 文章目录 0、总结1、字符分类、转换函数2、strlen的使用和模拟实现2.1 strlen的使用2.2 strlen的模拟实现 3、strcpy的使用和模拟实现3.1 strcpy的使用3.2 strcpy的模拟实现 4、strcat…...

三种机器学习类型

本文讲介绍三种机器学习类型&#xff1a;①监督学习&#xff0c;②无监督学习&#xff0c;③强化学习。我们主要了解监督学习和无监督学习即可。 下图介绍这三种机器学习类型的区别&#xff1a; 1 用来预测未来的监督学习 从有标签的训练数据中学习一个模型&#xff0c;用来…...

UE5 Set actor Location和 Set World Location 和 Set Relative Location 的区别

在 Unreal Engine 的蓝图里&#xff0c;SetRelativeLocation、SetWorldLocation 和 SetActorLocation 三个节点虽然都能改变物体位置&#xff0c;但作用对象和坐标空间&#xff08;Coordinate Space&#xff09;不同&#xff1a; 1. SetActorLocation 作用对象&#xff1a;整个…...

Glide 如何加载远程 Base64 图片

最近有个需求&#xff0c;后端给出的图片地址并不是正常的 URL&#xff0c;而且需要一个接口去请求&#xff0c;但是返回的是 base64 数据流。这里不关心为啥要这么多&#xff0c;原因有很多&#xff0c;可能是系统的问题&#xff0c;也可能是能力问题。当然作为我们 Android 程…...

JVM对象存储格式

引言 在Java虚拟机&#xff08;JVM&#xff09;中&#xff0c;对象的内存布局是一个重要的底层概念&#xff0c;它直接影响对象在内存中的存储方式和占用空间。了解对象存储格式不仅有助于优化程序性能&#xff0c;还能帮助我们更好地理解JVM的工作原理。本文将详细探讨对象存…...

3D Gaussian Splatting部分原理介绍和CUDA代码解读

本系列旨在帮助无CUDA代码经验的读者、以及3DGS的初学者理解代码逻辑。 3D GS论文原文链接&#xff1a;https://arxiv.org/abs/2308.04079 论文笔记链接&#xff1a;【论文笔记】3D Gaussian Splatting for Real-Time Radiance Field Rendering 【论文笔记】A Survey on 3D Ga…...

日本IT行业|salesforce开发语言占据的地位

在日本的IT行业中&#xff0c;Salesforce 开发语言处于一个较为专业但稳步增长的细分领域&#xff0c;并不是主流开发语言&#xff08;如 Java、Python、PHP&#xff09;&#xff0c;但其在某些行业和场景中地位越来越重要。 本篇以下是详细分析&#xff1a; Salesforce开发语言…...

1.1 点云数据获取方式——引言

图1-1-1点云建筑场景图 点云数据是指能够描述外部场景、对象表面的三维空间位置&#xff0c;并具有相关属性的点集&#xff0c;其每个离散点通常包括三维空间位置&#xff08;x,y,z&#xff09;以及强度、颜色等属性信息。大量分布的离散点集能够清晰而直接地描绘场景、对象的3…...

接入层架构演变

1、单体架构 请求过程 浏览器的请求通过 DNS Server 解析到指定的 IP 地址&#xff0c;浏览器通过 IP 地址访问 Web Server 缺点 当到达 Web Server 的性能瓶颈时&#xff08;瓶颈受到CPU&#xff0c;内存&#xff0c;io&#xff0c;带宽影响&#xff09;&#xff0c;无法进…...

python:sklearn 主成分分析(PCA)

参考书&#xff1a;《统计学习方法》第2版 第16章 主成分分析&#xff08;PCA&#xff09;示例 编写 test_pca_1.py 如下 # -*- coding: utf-8 -*- """ 主成分分析&#xff08;PCA&#xff09; """ import matplotlib.pyplot as plt from skl…...

力扣-数据结构-二叉树

94. 二叉树的中序遍历 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#x…...

嵌入式音视频实时通话EasyRTC打造设备安装与调试的高效远程解决方案

一、背景 在数字化浪潮席卷全球的今天&#xff0c;实时音视频通信技术已经成为众多领域不可或缺的重要组成部分。从智能家居到智能安防&#xff0c;从在线教育到远程医疗&#xff0c;人们对于高效、便捷、稳定且低延迟的音视频通信解决方案的需求日益迫切。而EasyRTC作为一款卓…...

AI 的未来是开源?DeepSeek 正在书写新篇章!

AI 的未来是开源&#xff1f;DeepSeek 正在书写新篇章&#xff01; 随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;越来越多的企业和研究机构开始关注 AI 的开放性和透明度。开源不仅能够促进技术创新&#xff0c;还能加速知识的传播和应用。在这个背景下…...

抢先体验全新极小大模型Qwen3:0.6B

全民都在期待DeepSeek-R2的发布,但是一不小心被阿里截胡了,2025 年 4 月 29 日,阿里巴巴发布并开源了通义千问 Qwen3 系列大模型。据 大模型镜像网站 上关于Qwen3的介绍: Qwen3 是 Qwen 系列中最新一代的大型语言模型,提供一整套密集模型和混合专家 (MoE) 模型。Qwen3 基…...

部署一个自己的Spring Ai 服务(deepseek/通义千问)

Spring Boot 无缝接入 DeepSeek 和通义千问请求日志记录及其ip黑白名单 SpringBoot版本 3.2.0 JDK 版本为17 redis 3.2.0 mybatis 3.0.3 依赖引入 关键依赖 <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-sp…...

第一讲 | 算法复杂度

算法复杂度 一、数据结构前言1、数据结构&#xff08;DS&#xff09;2、算法&#xff08;Algorithm&#xff09; 二、算法效率1、复杂度的概念 三、时间复杂度&#xff08;1&#xff09;、案例&#xff08;2&#xff09;、大O的渐进表示法&#xff08;3&#xff09;、时间复杂度…...

【运维】还原 Docker 启动命令的利器:runlike 与 docker-autocompose

&#x1f50d; 还原 Docker 启动命令的利器&#xff1a;runlike 与 docker-autocompose 实用教程 在日常使用 Docker 时&#xff0c;我们常常通过 docker run 启动容器&#xff0c;但有时候过了一段时间就忘记了当初使用的具体参数&#xff08;端口、挂载、环境变量等&#xf…...

IP属地是实时位置还是自己设置

刷微博、抖音时&#xff0c;评论区总能看到“IP属地”&#xff1f;这个突然冒出来的小标签&#xff0c;让不少网友摸不着头脑&#xff1a;‌IP属地是实时位置&#xff0c;还是可以自己设置&#xff1f;‌别急&#xff0c;今天咱们就来聊聊这个话题&#xff01; 1、什么是IP属地…...

Android WIFI体系

先说说WifiLock、MulticastLock 、IWificond WifiLock 允许应用强制保持 WiFi 活跃&#xff0c;即便设备处于休眠状态。如WIFI_MODE_FULL_HIGH_PERF&#xff1a;保持高性能 WiFi 活跃状态&#xff0c;适用于高带宽需求&#xff0c;如视频通话、流媒体。经测试有的场景能减少10…...

什么是静态住宅ip,跨境电商为什么要用静态住宅ip

在数字时代&#xff0c;IP地址不仅是设备联网的“ID”&#xff0c;更是跨境电商运营中的关键工具。尤其对于需要长期稳定、安全操作的场景&#xff0c;静态住宅IP逐渐成为行业首选。 一、什么是静态住宅IP&#xff1f; 静态住宅IP&#xff08;Static Residential IP&#xff0…...

常见位运算总结

目录 常见位运算总结 191:位1的个数 338&#xff1a;比特位计数 461:汉明距离 136&#xff1a;只出现一次的数字 260&#xff1a;只出现一次的数字III 常见位运算总结 191:位1的个数 链接&#xff1a;191. 位1的个数 - 力扣&#xff08;LeetCode&#xff09; class Sol…...

[密码学实战]SDF之对称运算类函数(四)

[密码学实战]SDF之对称运算类函数(四) 一、标准解读:GM/T 0018-2023核心要求 1.1 SDF接口定位 安全边界:硬件密码设备与应用系统间的标准交互层 功能范畴: #mermaid-svg-1jptduZFNFiRZ2lS {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16…...

【C++编程入门】:基本语法

上一篇提到了C关键字和缺省参数以及命名空间域&#xff0c;这篇继续分享C入门语法&#xff0c;把基本语法掌握扎实后面学习更才能更轻松一些。 目录 引用 引用的特性 常引用 内联函数 auto关键字 引用 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&am…...

区块链最佳框架:Truffle vs Hardhat vs Brownie

区块链技术的快速发展使得智能合约开发成为主流&#xff0c;而选择合适的开发框架是提升效率的关键。目前&#xff0c;Truffle、Hardhat和Brownie是三大主流框架&#xff0c;它们各有特点&#xff0c;适用于不同的开发场景和开发者偏好。本文将从功能、生态系统、适用人群等角度…...

Apache Flink的架构设计与运行流程说明

在大数据领域&#xff0c;实时计算的重要性随着业务需求的爆发式增长愈发凸显。从电商的实时销量监控到金融的高频交易风控&#xff0c;从物联网设备的实时告警到社交平台的热点追踪&#xff0c;企业对“秒级甚至毫秒级”数据处理能力的需求已成为刚需。在众多实时计算框架中&a…...

AI+零售:智能推荐、无人店与供应链管理的未来

AI零售&#xff1a;智能推荐、无人店与供应链管理的未来 系统化学习人工智能网站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目录 AI零售&#xff1a;智能推荐、无人店与供应链管理的未来摘要引言一、智能推荐系统&#xff1a;从流量收割到用…...

华为云IoT平台与MicroPython实战:从MQTT协议到物联网设备开发

目录 前言 1. 华为云 1.1. 创建实例 1.2. 创建产品 1.3. 编辑服务模型 1.4. 注册设备 1.4.1. 复制设备连接参数 1.5. 连接参考代码 2. micropython版-物联网 2.1. 环境搭建 2.2. 实现步骤 2.3. 示例代码 结语 前言 物联网&#xff08;IoT&#xff09;技术的快速发…...

【Linux】Linux内核模块开发

Linux内核模块开发 零、关于 1、概述 最近在学习Linux相关的东西&#xff0c;学习了U-Boot的编译&#xff0c;Linux的编译&#xff0c;能够在开发板上运行自己编译的U-Boot和Linux了&#xff0c;那么接下来就是在自己编译的Linux上做应用级或者系统级的开发了。本文以字符设…...

linux 下查看指定进程的内存CPU占用情况(用于程序崩溃类的排查)

在程序开发过程中&#xff0c;如果程序较为庞大&#xff0c;逻辑较为复杂时&#xff0c;容易出现运行时崩溃的问题。导致的原因有很多&#xff0c;我这里只对较为通用的内容占用情况作记录&#xff0c;如程序中对文件描述符打开未关闭&#xff08;导致fd积攒过多超过了系统的标…...

ASP.NET MVC​ 入门指南五

26. 响应式设计与移动开发 26.1 响应式视图设计 为了使 MVC 应用程序在不同设备上都能提供良好的用户体验&#xff0c;需要采用响应式设计。可以使用 CSS 框架如 Bootstrap 来实现响应式布局。 引入 Bootstrap&#xff1a;在项目中引入 Bootstrap 的 CSS 和 JavaScript 文件。…...

字节跳动社招面经 —— BSP驱动工程师(4)

接前一篇文章&#xff1a;字节跳动社招面经 —— BSP驱动工程师&#xff08;3&#xff09; 本文内容参考&#xff1a; 嵌入式硬件平台修改启动地址-CSDN博客 特此致谢&#xff01; 上一回开始针对于“嵌入式充电站”发的一篇文章字节跳动社招面经——BSP驱动工程师中的面试题…...

Spring MVC中自定义日期类型格式转换器

在Spring MVC中&#xff0c;自定义日期类型格式转换器可以通过实现Converter接口或使用DateTimeFormat注解。以下是两种方法的详细说明&#xff1a; 方法一&#xff1a;全局自定义转换器&#xff08;推荐&#xff09; 1. 创建日期转换器类 实现 org.springframework.core.con…...

【3D 地图】无人机测绘制作 3D 地图流程 ( 无人机采集数据 | 地图原始数据处理原理 | 数据处理软件 | 无人机测绘完整解决方案 )

文章目录 一、无人机采集数据1、多角度影像数据2、定位与姿态数据 二、无人机采集数据处理原理1、空三解算2、密集点云生成与三维重建3、地形与正射影像生成4、三维模型优化与瓦片化 三、无人机影像处理软件介绍 一、无人机采集数据 无人机原始数据采集 : 多角度影像数据 : 多…...

arduino Nano介绍

【仅供学习&#xff0c;具体参数参考官网或销售商】 Arduino Nano 是一款基于 ATmega328P 微控制器&#xff08;或 ATmega168 旧版&#xff09;的紧凑型开发板&#xff0c;专为嵌入式项目和原型设计而设计。 以下是Arduino Nano V3.0 328P详细介绍&#xff1a; 主要特性 微…...

解决 Flutter 在 iOS 真机上构建失败的问题

在开发 Flutter 应用时&#xff0c;有时会在尝试将应用部署到 iOS 真机时遇到构建失败的问题。错误信息通常类似于以下内容&#xff1a; Could not build the precompiled application for the device. Uncategorized (Xcode): Timed out waiting for all destinations matchi…...

【办公类-89-03】20250429AI写的研讨记录,清除格式,统一格式,名字替换。部分加粗,添加页眉

背景需求: 检查自即,需要AI一下院内的五次科研培训记录。 本次用了豆包 豆包写的不错,也是“水字数”的高手 把每次培训内容贴到WORD里 把AI资料贴到WORD里,发现问题: 1、字体、段落什么都是不统一的,需要统一改成宋体小四,1.5倍行距 2、十个研讨人也要改成真人。就找…...

react-native 安卓APK打包流程

一、使用keytool命令生成一个签名密钥 $ keytool -genkeypair -v -storetype PKCS12 -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000 在 Windows 上keytool命令放在 JDK 的 bin 目录中&#xff08;比如C:\Program Files\…...

Android Studio中OpenCV应用详解:图像处理、颜色对比与OCR识别

文章目录 一、OpenCV在Android中的集成与配置1.1 OpenCV简介1.2 在Android Studio中集成OpenCV1.2.1 通过Gradle依赖集成1.2.2 通过模块方式集成1.2.3 初始化OpenCV 1.3 OpenCV基础类介绍 二、指定区域图像抓取与对比2.1 图像抓取基础2.2 指定区域图像抓取实现2.2.1 从Bitmap中…...

企业办公协同平台安全一体化生态入住技术架构与接口标准分析报告

全球组织数字化与智能化背景下 企业办公协同平台安全一体化生态入住技术架构与接口标准分析报告 一、背景与市场需求 市场规模与增量 根据Statista数据&#xff0c;全球协同办公平台市场规模预计从2023年的$480亿增长至2027年的$900亿&#xff0c;年复合增长率&#xff08;CAG…...

从零搭建体育比分网站:技术选型与API调用实战(附完整源码)

一、前言&#xff1a;为什么选择体育比分项目&#xff1f; 体育数据网站是练手全栈开发的绝佳项目&#xff0c;涉及&#xff1a; ✅ 前端&#xff08;实时数据渲染、可视化图表&#xff09; ✅ 后端&#xff08;API对接、数据缓存、高并发优化&#xff09; ✅ 数据库&#xff…...

非凸科技受邀出席AI SPARK活动,共探生成式AI驱动金融新生态

4月19日&#xff0c;由AI SPARK社区主办的“生成式AI创新与应用构建”主题沙龙在北京举行。活动聚焦生成式AI的技术突破与产业融合&#xff0c;围绕大模型优化、多模态应用、存内计算等前沿议题展开深度探讨。非凸科技受邀出席并发表主题演讲&#xff0c;深入解析金融垂直大模型…...