Apache Serf 常用工具与 API 详解
Apache Serf 1.3.10 常用工具与 API 详解,面向 C 语言开发工程师:梳理 apr-config、pkgconfig、ldconfig 等辅助工具链与头文件布局,讲解 serf_context 事件循环、connection 连接、request 与 response、bucket brigades 桶体系四大核心概念与函数分类速查,并附实测编译运行的 HTTP GET 完整示例与契约清单。
Apache Serf 常用工具与 API 详解
目标:面向基于 Apache Serf 1.3.10 进行 C 语言开发的工程师,梳理周边辅助工具链与头文件布局,讲清 serf_context(事件循环上下文)、connection(连接)、request/response(请求与响应)、bucket brigades(桶体系)四大核心概念及常用函数分类,并给出一段实测编译运行的最小 HTTP GET 示例。所有代码与回显均在 Ubuntu 22.04.5 环境实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS
- 编译器:GCC 11.4.0
- Serf 版本:1.3.10(前缀
/usr/local/serf,头文件/usr/local/serf/include/serf-1/,库libserf-1.so.1) - APR:1.7.0;APR-util:1.6.1
- 本机 HTTP 服务:python3
http.server(8000 端口) - 演示账号:root
二、常用工具概览
Serf 本体只提供库与头文件,没有命令行工具(/usr/local/serf/bin 不存在)。日常开发依赖的是周边四类工具:
| 工具 | 用途 | 实测命令示例 |
|---|---|---|
| apr-1-config / apu-1-config | 取 APR/APU 的编译旗标与库路径,Serf 构建与应用编译都靠它定位依赖 | apr-1-config --version(1.7.0)、apu-1-config --includes |
| pkg-config | 读 serf-1.pc 自动展开编译链接参数 | pkg-config --modversion serf-1(1.3.10) |
| ldconfig | 注册/查看动态库缓存,验证安装是否就位 | ldconfig -p | grep serf |
| ldd | 核对程序运行时实际加载的 serf 库路径 | ldd /opt/serf-demo | grep serf |
ldconfig -p | grep serf
libserf-1.so.1 (libc6,x86-64) => /usr/local/serf/lib/libserf-1.so.1
libserf-1.so (libc6,x86-64) => /usr/local/serf/lib/libserf-1.so
头文件清单与分工(/usr/local/serf/include/serf-1/):
| 头文件 | 内容 |
|---|---|
| serf.h | 上下文、连接、请求、响应回调、SSL 上下文、认证回调等核心 API 与类型 |
| serf_bucket_types.h | 全部 bucket 类型的构造与读取函数(bucket brigades 体系) |
| serf_bucket_util.h | 桶分配器(serf_bucket_alloc_t)、桶读取辅助等工具函数 |
三、前置条件
- 已按《Apache Serf 安装指南》篇完成 Serf 1.3.10 安装与
ldconfig注册。 - 已按《Apache Serf 构建并运行示例应用指南》篇准备本机 HTTP 服务:
mkdir -p /var/www/demo
echo "<h1>Hello from Apache Serf demo</h1><p>Serf 1.3.10 async HTTP client test page.</p>" > /var/www/demo/index.html
cd /var/www/demo && nohup python3 -m http.server 8000 >/tmp/httpd.log 2>&1 &
四、核心概念总览
Serf 的运行模型是一条「上下文 → 连接 → 请求 → 桶」的四级流水,全部回调异步触发:
serf_context_t(事件循环)
└── serf_connection_t(到同一主机的 TCP 连接,可复用/排队多请求)
└── serf_request_t(一次 HTTP 请求,挂 setup/acceptor/handler 三回调)
├── setup → serf_bucket_request_create 组装请求桶
├── acceptor → 把响应字节流包装成响应桶
└── handler → serf_bucket_response_status 读状态行 + serf_bucket_read 读响应体
bucket(桶) 是 Serf 的数据抽象:请求体、响应体、socket 字节流、加密层都被抽象为桶;桶之间可以叠加包装形成「桶链」,如 socket 桶 → barrier 桶 → response 桶。桶从 serf_bucket_alloc_t 分配器分配,APR 内存池驱动生命周期。
五、核心 API 详解
5.1 上下文 API(serf_context_t)
事件循环核心对象,管理连接表、认证状态与 poll 集合。
| 函数 | 说明 |
|---|---|
serf_context_create(apr_pool_t *pool) | 创建上下文,返回 serf_context_t* |
serf_context_run(ctx, duration, pool) | 驱动一轮事件:非阻塞地执行就绪的连接/请求事件。duration 单位微秒,SERF_DURATION_NOBLOCK(0) 立即返回,SERF_DURATION_FOREVER 长阻塞;超时返回 APR_TIMEUP,需在应用层循环调用 |
💡 实测事件循环写法:
while (!app.done) { status = serf_context_run(ctx, 100000, pool); if (status && !APR_STATUS_IS_TIMEUP(status)) break; },单次 100ms 上限,兼顾响应及时性与退出能力。
5.2 连接 API(serf_connection_t)
一个连接对应到一台主机的 TCP 会话,请求按序在其上排队。
| 函数 | 说明 |
|---|---|
serf_connection_create(ctx, address, setup, setup_baton, closed, closed_baton, pool) | 按 apr_sockaddr_t 建连。⚠️ 实测 1.3.10 存在缺陷:该接口不设置 conn->host_url,认证模块以 NULL 键查哈希表导致段错误,不要在生产代码使用 |
serf_connection_create2(&conn, ctx, host_info, setup, setup_baton, closed, closed_baton, pool) | 按 apr_uri_t 建连(先 apr_uri_parse 解析 URL),生成完整 host_url,1.3.10 实测推荐唯一入口;返回 apr_status_t |
serf_connection_request_create(conn, setup, setup_baton) | 排队一个请求,返回 serf_request_t*;setup 回调在事件循环内被调用 |
serf_connection_close(conn) | 关闭连接 |
serf_connection_reset(conn, pool) | 清空连接上未发出的请求队列 |
serf_connection_set_max_outstanding_requests(conn, max_requests) | 限制连接上并发排队请求数,做背压 |
连接级 setup 回调(conn_setup,1.3.10 必填):
typedef apr_status_t (*serf_connection_setup_t)(apr_socket_t *skt,
serf_bucket_t **read_bkt, serf_bucket_t **write_bkt,
void *setup_baton, apr_pool_t *pool);
职责:把 apr_socket_t 包装成输入桶 serf_bucket_socket_create(skt, alloc);HTTPS 场景在此叠加 serf_bucket_ssl_decrypt_create / serf_bucket_ssl_encrypt_create。
⚠️ 实测教训:把 setup 传 NULL 会在建连阶段空函数指针调用段错误(gdb 栈顶
#0 0x0,位于prepare_conn_streams → serf__open_connections → serf_context_prerun)。
5.3 请求与响应 API(serf_request_t / response)
| 函数/回调 | 说明 |
|---|---|
serf_request_get_alloc(request) | 取请求级桶分配器,请求/响应桶都用它分配 |
serf_request_bucket_request_create(request, method, uri, body, allocator) | 组装请求桶:方法、URI、可选请求体桶(GET 传 NULL) |
serf_bucket_request_set_root(bucket, root_url) | 设置相对 URI 的根路径前缀 |
serf_request_cancel(request, status) | 取消排队/进行中的请求 |
| serf_request_setup_t(回调) | 签名含 serf_bucket_t **req_bkt 与 acceptor/handler 指针出口,在事件循环内组装请求并挂接响应处理 |
| serf_response_acceptor_t(回调) | 收到响应字节流时调用,标准实现:serf_bucket_barrier_create(stream, alloc) 后 serf_bucket_response_create(barrier, alloc) |
| serf_response_handler_t(回调) | 响应数据处理回调;response == NULL 表示连接中断 |
serf_bucket_response_status(bkt, &sline) | 解析状态行到 serf_status_line(.code 即 HTTP 状态码,.version 为协议版本) |
handler 返回值契约(实测关键):serf 会反复调用 handler 直到返回非 APR_SUCCESS——
- 响应读取完成(
serf_bucket_read返回 EOF):必须return APR_EOF; - 暂无更多数据:
return APR_EAGAIN; - 返回
APR_SUCCESS表示「还有活干」,serf 会立即再次回调——实测读尽后仍返回 SUCCESS 会造成 100% CPU 空转。
5.4 Bucket brigades 桶体系(serf_bucket_types.h)
桶是 Serf 的统一数据抽象,按职能分四类(常用成员与构造函数):
| 类别 | 桶类型与构造函数 | 说明 |
|---|---|---|
| 数据源桶 | serf_bucket_simple_create(内存)、serf_bucket_file_create(文件)、serf_bucket_mmap_create(内存映射) | 请求体/上传数据来源 |
| 传输/协议桶 | serf_bucket_socket_create(socket 字节流)、serf_bucket_response_create(HTTP 响应解析)、serf_bucket_request_create(HTTP 请求组装)、serf_bucket_barrier_create(屏障:防止上层读穿底层流) | 连接与协议层 |
| 变换桶 | serf_bucket_dechunk_create(去 chunked)、serf_bucket_deflate_create(gzip/deflate 解压)、serf_bucket_limit_create(限长)、serf_bucket_iovec_create(聚合写) | 逐层包装实现功能叠加 |
| SSL 桶 | serf_ssl_context_create、serf_bucket_ssl_encrypt_create、serf_bucket_ssl_decrypt_create | HTTPS 传输层 |
桶的统一读取接口(所有桶都适用):
apr_status_t serf_bucket_read(serf_bucket_t *bucket, apr_size_t len,
const char **data, apr_size_t *len);
/* 返回 APR_SUCCESS(还有数据)、APR_EAGAIN(暂时没有)、APR_EOF(读尽) */
配套辅助:serf_bucket_headers_get/set(头字段操作,响应头可用 serf_bucket_response_get_headers 取出)、serf_bucket_allocator_create(pool, alloc, free)(桶分配器)。
5.5 认证与 SSL 速查
- 认证:
serf_config_authn_types(ctx, SERF_AUTH_BASIC | SERF_AUTH_DIGEST | SERF_AUTH_NEGOTIATE)声明启用范围;serf_config_credentials_callback(ctx, callback)挂凭据回调,回调内通过serf_authn_provider填用户名口令。 - SSL:
serf_ssl_context_create(pool)创建后挂到 encrypt/decrypt 桶;serf_ssl_device_set_key_cert配客户端证书,serf_ssl_server_cert_callback_set配服务器证书校验。
六、实测最小示例(HTTP GET 全流程)
以下示例整合 5.1–5.3 全部核心 API,实测编译运行通过(完整逐行讲解见《Apache Serf 构建并运行示例应用指南》篇):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <apr.h>
#include <apr_pools.h>
#include <apr_uri.h>
#include "serf.h"
typedef struct {
int done;
int status_code;
char body[65536];
size_t blen;
serf_bucket_alloc_t *bkt_alloc;
} app_ctx_t;
/* 连接级回调(必填):socket 包装成输入桶 */
static apr_status_t conn_setup(apr_socket_t *skt, serf_bucket_t **input_bkt,
serf_bucket_t **output_bkt, void *setup_baton,
apr_pool_t *pool)
{
app_ctx_t *ctx = setup_baton;
*input_bkt = serf_bucket_socket_create(skt, ctx->bkt_alloc);
return APR_SUCCESS;
}
/* acceptor:barrier + response 包装响应流 */
static serf_bucket_t *accept_response(serf_request_t *request,
serf_bucket_t *stream,
void *acceptor_baton, apr_pool_t *pool)
{
serf_bucket_alloc_t *alloc = serf_request_get_alloc(request);
serf_bucket_t *barrier = serf_bucket_barrier_create(stream, alloc);
return serf_bucket_response_create(barrier, alloc);
}
/* handler:读状态行 + 循环读响应体;EOF 返回 APR_EOF */
static apr_status_t handle_response(serf_request_t *request,
serf_bucket_t *response,
void *handler_baton, apr_pool_t *pool)
{
app_ctx_t *ctx = handler_baton;
serf_status_line sl;
apr_status_t status;
if (response == NULL) {
fprintf(stderr, "[demo] connection closed without response\n");
ctx->done = 1;
return APR_SUCCESS;
}
status = serf_bucket_response_status(response, &sl);
if (status) {
return status;
}
ctx->status_code = sl.code;
while (1) {
const char *data;
apr_size_t len;
status = serf_bucket_read(response, 4096, &data, &len);
if (SERF_BUCKET_READ_ERROR(status)) {
return status;
}
if (len > 0 && ctx->blen + len < sizeof(ctx->body)) {
memcpy(ctx->body + ctx->blen, data, len);
ctx->blen += len;
ctx->body[ctx->blen] = '\0';
}
if (APR_STATUS_IS_EOF(status)) {
ctx->done = 1;
return APR_EOF;
}
if (APR_STATUS_IS_EAGAIN(status)) {
return status;
}
}
ctx->done = 1;
return APR_SUCCESS;
}
/* request setup:组装 GET 请求桶并挂回调 */
static apr_status_t setup_request(serf_request_t *request, void *setup_baton,
serf_bucket_t **req_bkt,
serf_response_acceptor_t *acceptor,
void **acceptor_baton,
serf_response_handler_t *handler,
void **handler_baton, apr_pool_t *pool)
{
app_ctx_t *ctx = setup_baton;
serf_bucket_alloc_t *alloc = serf_request_get_alloc(request);
*req_bkt = serf_request_bucket_request_create(request, "GET", "/", NULL, alloc);
*acceptor = accept_response;
*acceptor_baton = ctx;
*handler = handle_response;
*handler_baton = ctx;
return APR_SUCCESS;
}
int main(int argc, char **argv)
{
apr_status_t status;
apr_pool_t *pool;
serf_context_t *ctx;
serf_connection_t *conn;
apr_uri_t host_info;
app_ctx_t app;
const char *url = (argc > 1) ? argv[1] : "http://127.0.0.1:8000";
int loops = 0;
memset(&app, 0, sizeof(app));
apr_initialize();
atexit(apr_terminate);
apr_pool_create(&pool, NULL);
ctx = serf_context_create(pool);
app.bkt_alloc = serf_bucket_allocator_create(pool, NULL, NULL);
apr_uri_parse(pool, url, &host_info);
status = serf_connection_create2(&conn, ctx, host_info,
conn_setup, &app, NULL, NULL, pool);
if (status != APR_SUCCESS) {
return 1;
}
serf_connection_request_create(conn, setup_request, &app);
while (!app.done && loops < 200) {
status = serf_context_run(ctx, 100000, pool);
if (status && !APR_STATUS_IS_TIMEUP(status)) {
return 1;
}
loops++;
}
printf("HTTP status: %d\n", app.status_code);
printf("Body bytes : %zu\n", app.blen);
printf("---- body ----\n%s\n--------------\n", app.body);
serf_connection_close(conn);
apr_pool_destroy(pool);
return app.done ? 0 : 2;
}
编译:
cd /opt
gcc serf-demo.c -o serf-demo \
-I/usr/local/serf/include/serf-1 \
$(apr-1-config --cflags --includes) \
$(apu-1-config --includes) \
-L/usr/local/serf/lib -lserf-1 -lapr-1 -laprutil-1
运行与实测输出:
/opt/serf-demo http://127.0.0.1:8000
HTTP status: 200
Body bytes : 84
---- body ----
<h1>Hello from Apache Serf demo</h1><p>Serf 1.3.10 async HTTP client test page.</p>
--------------
七、API 使用核对清单
写一个 Serf 客户端程序时按此清单自查,可规避本版本实测暴露的全部坑:
⚠️ 注意:
- 建连用
serf_connection_create2(配apr_uri_parse),不要用 sockaddr 版serf_connection_create(host_url 缺失,认证路径段错误)。serf_connection_create2的 setup 回调必须提供,内部完成serf_bucket_socket_create包装。- acceptor 标准三行:
serf_request_get_alloc取分配器 →serf_bucket_barrier_create屏障 →serf_bucket_response_create响应桶。- handler 收尾契约:读尽返回
APR_EOF、暂无数据返回APR_EAGAIN,绝不返回APR_SUCCESS蒙混。apr_initialize()/apr_terminate()、内存池、桶分配器三件套先于一切 serf 调用建立,池生命周期覆盖全部对象。- 事件循环按「应用完成标志 + serf_context_run 超时」双条件退出,避免 FOREVER 阻塞或 0 时长空转。
八、常见问题(FAQ)
Q1:serf 有命令行客户端工具吗?
没有。Serf 只发布库与头文件;HTTP 收发需按本文 API 自行编写,或借助 libcurl/命令行工具做联通性验证(如本机 wget -q -O- http://127.0.0.1:8000/)。
Q2:serf_request_get 这样的函数存在吗?
不存在。发起 GET 的正确路径是:serf_connection_request_create 排队请求,在 serf_request_setup_t 回调里用 serf_request_bucket_request_create(request, "GET", uri, NULL, alloc) 组装 GET 请求桶——「GET」只是方法字符串参数。
Q3:一个连接能发多个请求吗?
能。serf_connection_request_create 可连续排队多个请求,Serf 按 HTTP/1.1 语义在同一连接上按序收发(Keep-Alive 复用);排队过多时用 serf_connection_set_max_outstanding_requests 做背压。
Q4:handler 里能读请求头吗?
能。响应桶上 serf_bucket_response_get_headers(response) 取头桶,再 serf_bucket_headers_get(headers, "Content-Type") 逐项读。状态行则用 serf_bucket_response_status。
Q5:如何向服务器上传数据(POST)?
serf_request_bucket_request_create(request, "POST", uri, body_bucket, alloc):先构造请求桶时把 serf_bucket_simple_create(data, len, NULL, NULL, alloc) 之类的数据源桶作为 body 传入,Serf 自动补 Content-Length。
九、参考链接
- 头文件 API 文档(权威):
/usr/local/serf/include/serf-1/serf.h、serf_bucket_types.h、serf_bucket_util.h - 官方示例:源码包
test/serf_get.c - 设计文档:源码包
design-guide.txt - 关联篇目:《Apache Serf 安装指南》《Apache Serf 构建并运行示例应用指南》《Apache Serf 配置参数详解》