Apache Serf 常用工具与 API 详解

Apache Serf 1.3.10 常用工具与 API 详解,面向 C 语言开发工程师:梳理 apr-config、pkgconfig、ldconfig 等辅助工具链与头文件布局,讲解 serf_context 事件循环、connection 连接、request 与 response、bucket brigades 桶体系四大核心概念与函数分类速查,并附实测编译运行的 HTTP GET 完整示例与契约清单。

Apache SerfApache Serf2026/10/12

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_createHTTPS 传输层

桶的统一读取接口(所有桶都适用):

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 客户端程序时按此清单自查,可规避本版本实测暴露的全部坑:

⚠️ 注意:

  1. 建连用 serf_connection_create2(配 apr_uri_parse),不要用 sockaddr 版 serf_connection_create(host_url 缺失,认证路径段错误)。
  2. serf_connection_create2 的 setup 回调必须提供,内部完成 serf_bucket_socket_create 包装。
  3. acceptor 标准三行:serf_request_get_alloc 取分配器 → serf_bucket_barrier_create 屏障 → serf_bucket_response_create 响应桶。
  4. handler 收尾契约:读尽返回 APR_EOF、暂无数据返回 APR_EAGAIN,绝不返回 APR_SUCCESS 蒙混。
  5. apr_initialize()/apr_terminate()、内存池、桶分配器三件套先于一切 serf 调用建立,池生命周期覆盖全部对象。
  6. 事件循环按「应用完成标志 + 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 配置参数详解》