Fluent Bit 与 nghttp2 中的 nghttp2_session_callbacks_set_send_callback 指南:HTTP/2 发送回调的设置、语义与工程实践
2026/9/17 12:01:05 网站建设 项目流程

Fluent Bit 与 nghttp2 中的 nghttp2_session_callbacks_set_send_callback 指南:HTTP/2 发送回调的设置、语义与工程实践

【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit

Fluent Bit 仓库将 nghttp2-1.65.0 作为内置依赖(见 lib/nghttp2-1.65.0),其 HTTP/2 客户端与服务器实现都基于 nghttp2 会话回调机制。本文以 nghttp2 官方文档 nghttp2_session_callbacks_set_send_callback.rst 为主线,系统讲解 send 回调(发送回调)的注册方式、回调类型语义、非阻塞返回值约定,以及它在 Fluent Bit 实际代码中的落地用法,帮助读者掌握"何时必须注册 send 回调、如何正确实现、以及底层库如何调用它"这一完整链路。

1. 函数签名与声明位置

该 API 的官方文档位于 lib/nghttp2-1.65.0/doc/nghttp2_session_callbacks_set_send_callback.rst,头文件声明位于 lib/nghttp2-1.65.0/lib/includes/nghttp2/nghttp2.h。

#include <nghttp2/nghttp2.h> void nghttp2_session_callbacks_set_send_callback( nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback);

1.1 参数说明

参数类型含义
cbsnghttp2_session_callbacks *nghttp2_session_callbacks_new()创建的回调集合对象,本函数把 send 回调写入该结构体
send_callbacknghttp2_send_callback会话需要向对端发送数据时被调用的回调函数指针

1.2 底层实现:回调如何被"挂"到会话上

从源码实现看,这个 setter 本质上是一次结构体字段赋值。见 lib/nghttp2-1.65.0/lib/nghttp2_callbacks.c:

void nghttp2_session_callbacks_set_send_callback( nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback) { cbs->send_callback = send_callback; } void nghttp2_session_callbacks_set_send_callback2( nghttp2_session_callbacks *cbs, nghttp2_send_callback2 send_callback) { cbs->send_callback2 = send_callback; }

回调集合结构体nghttp2_session_callbacks中同时存在send_callbacksend_callback2两个字段:前者承载旧版ssize_t签名的回调,后者承载新版nghttp2_ssize签名的回调。应用程序在调用nghttp2_session_client_new()/nghttp2_session_server_new()创建会话时传入该回调集合,库在会话内部需要发送数据时即会取出对应字段并触发调用。

2. 回调的调用语义

2.1 触发时机

该回调在"会话想要向远程对端发送数据"时被调用,典型触发场景包括:

  • 发送 HEADERS 帧(请求/响应头);
  • 发送 DATA 帧(请求体或响应体);
  • 发送 WINDOW_UPDATE、SETTINGS、PING、GOAWAY、RST_STREAM 等连接管理帧。

2.2 非阻塞返回值约定

回调的实现必须遵守 nghttp2 的返回值约定,这是 HTTP/2 非阻塞 I/O 正确性的核心:

返回值含义
成功发送的字节数(> 0)已把data中最多length字节写入网络,返回实际写入的字节数
NGHTTP2_ERR_WOULDBLOCK当前连一个字节都无法在不阻塞的情况下写出,库将稍后重试
NGHTTP2_ERR_CALLBACK_FAILURE发生了其他错误,会话将终止连接

2.3 关于length可能很小

官方文档特别提醒:length可能非常小。如果应用禁用了 Nagle 算法(TCP_NODELAY),直接把小块data写入网络栈会产生大量小报文,效率极低。因此,应用应当自行负责把小块数据缓冲起来,合并成较大块再统一写出。

3.nghttp2_send_callback类型与弃用说明

3.1 函数指针类型定义

旧版回调类型定义在 lib/nghttp2-1.65.0/lib/includes/nghttp2/nghttp2.h:

typedef ssize_t (*nghttp2_send_callback)(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data);

各参数含义:

  • session:发起发送的 nghttp2 会话对象;
  • data:指向待发送数据的缓冲区;
  • lengthdata中的字节数;
  • flags:当前未使用,恒为 0;
  • user_data:创建会话时(nghttp2_session_client_new()nghttp2_session_server_new())传入的第三个参数,通常用于携带连接上下文。

3.2 弃用警告与替代方案

关联文档明确标注该 API 为Deprecated(已弃用),建议改用:

  • nghttp2_session_callbacks_set_send_callback2()(setter);
  • nghttp2_send_callback2(回调类型);
  • nghttp2_session_mem_send2()(配套的内存序列化发送函数)。

新版回调类型nghttp2_send_callback2与旧版唯一的差异是返回类型从ssize_t换成了nghttp2_ssize(库自定义的符号类型),参数列表完全一致,定义见 lib/nghttp2-1.65.0/lib/includes/nghttp2/nghttp2.h:

typedef nghttp2_ssize (*nghttp2_send_callback2)(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data);

新版 setter 的文档位于 lib/nghttp2-1.65.0/doc/nghttp2_session_callbacks_set_send_callback2.rst。

4. 什么情况下可以不注册 send 回调

文档明确指出:如果应用只使用nghttp2_session_mem_send()(或新版nghttp2_session_mem_send2())将数据序列化到内存缓冲区,则无需注册该回调

原因在于 nghttp2 提供了两种发送模式:

模式发送函数数据去向是否需要 send 回调
内存序列化模式nghttp2_session_mem_send()/nghttp2_session_mem_send2()由调用方提供的内存缓冲区不需要
回调驱动模式nghttp2_session_send()由 send 回调直接写出必须注册

从 nghttp2.h 头文件注释可以进一步印证这一点(见 lib/nghttp2-1.65.0/lib/includes/nghttp2/nghttp2.h):使用nghttp2_session_send()的应用程序必须指定 send 回调,库才会把待发送的帧数据交回给应用。

5. 完整实现示例:官方示例程序

5.1 注册回调

官方示例 examples/client.c 展示了标准注册流程——先创建回调集合,再用 setter 挂上回调:

nghttp2_session_callbacks_new(&callbacks); nghttp2_session_callbacks_set_send_callback2(callbacks, send_callback); nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, on_frame_send_callback);

5.2 回调实现:基于 SSL 的非阻塞发送

examples/client.c 中的send_callback是"非阻塞 + 错误码映射"的典型范式:

static nghttp2_ssize send_callback(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data) { struct Connection *connection; int rv; (void)session; (void)flags; connection = (struct Connection *)user_data; connection->want_io = IO_NONE; ERR_clear_error(); rv = SSL_write(connection->ssl, data, (int)length); if (rv <= 0) { int err = SSL_get_error(connection->ssl, rv); if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) { connection->want_io = (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE); rv = NGHTTP2_ERR_WOULDBLOCK; } else { rv = NGHTTP2_ERR_CALLBACK_FAILURE; } } return rv; }

这段代码展示了三个关键工程点:

  1. 通过user_data取回连接上下文(这里是struct Connection *),并先清除 I/O 等待状态;
  2. SSL 层的SSL_ERROR_WANT_WRITE/SSL_ERROR_WANT_READ一律映射为NGHTTP2_ERR_WOULDBLOCK,让 nghttp2 知道需要等待事件循环重新调度;
  3. 其余错误统一映射为NGHTTP2_ERR_CALLBACK_FAILURE,交由库层终止会话。

6. 仓库内的工程落地:Fluent Bit 的 HTTP/2 发送回调

6.1 基于nghttp2_session_send()的回调驱动模式

Fluent Bit 的 HTTP/2 客户端在 src/flb_http_client_http2.c 注册回调:

nghttp2_session_callbacks_set_send_callback(callbacks, http2_send_callback);

其实现位于 src/flb_http_client_http2.c:不直接写网络,而是把序列化出的帧数据追加到应用层缓冲outgoing_data中,由上层统一调度写出:

static ssize_t http2_send_callback(nghttp2_session *inner_session, const uint8_t *data, size_t length, int flags, void *user_data) { cfl_sds_t resized_buffer; struct flb_http2_client_session *session; session = (struct flb_http2_client_session *) user_data; resized_buffer = cfl_sds_cat(session->parent->outgoing_data, (const char *) data, length); if (resized_buffer == NULL) { return NGHTTP2_ERR_CALLBACK_FAILURE; } session->parent->outgoing_data = resized_buffer; return length; }

值得注意的两个细节:

  • 返回值即"缓冲写入成功"信号:只要cfl_sds_cat成功完成(不会阻塞),就返回length,表示这length字节已全部被应用接管;
  • 错误映射只保留NGHTTP2_ERR_CALLBACK_FAILURE:内存分配失败属于确定性错误,直接返回该错误码让库终止会话,不适用NGHTTP2_ERR_WOULDBLOCK(因为没有"稍后重试"的必要)。

6.2 会话发送的调用链

注册之后,Fluent Bit 在多个位置调用nghttp2_session_send()驱动数据产出,例如:

  • src/flb_http_client_http2.c、src/flb_http_client_http2.c、src/flb_http_client_http2.c;
  • src/http_server/flb_http_server_http2.c、src/http_server/flb_http_server_http2.c、src/http_server/flb_http_server_http2.c、src/http_server/flb_http_server_http2.c。

这些调用的作用是把会话内部待发送的帧(HEADERS、DATA、SETTINGS 等)通过已注册的 send 回调产出字节流,这正是"会话 → send 回调 → 网络/缓冲"整条链路的驱动源头。HTTP/2 服务器端的回调集合创建与注册流程可对照查看 src/http_server/flb_http_server_http2.c。

7. 实战:两种发送模式的取舍

结合文档语义与仓库代码,在集成 nghttp2 时可按以下准则选择发送模式:

选择回调驱动模式(nghttp2_session_send()+ send 回调)当:

  • 事件循环由你掌控,希望在每次调用中"能发多少发多少";
  • 需要精确控制非阻塞语义,把WOULDBLOCK与事件循环的读写就绪通知联动;
  • 数据最终需要经过自定义封装(如 Fluent Bit 的cfl_sds缓冲、SSL/TLS 层)再写出。

选择内存序列化模式(nghttp2_session_mem_send()/mem_send2())当:

  • 数据要交给第三方 I/O 框架(如 libuv、自研 epoll 封装)写出;
  • 希望完全绕开回调注册,把 nghttp2 视为纯"帧序列化器";
  • 此时不必注册 send 回调,从mem_send返回的缓冲区直接送入你的发送队列即可。

8. 小结与进一步阅读

nghttp2_session_callbacks_set_send_callback是 nghttp2 回调驱动发送模式的入口 API:它把"会话想发数据"这一事件与"应用实际写出字节"这一动作连接起来。核心要点回顾:

  1. 回调必须返回实际发送字节数,或在无法发送时返回NGHTTP2_ERR_WOULDBLOCK,其他错误返回NGHTTP2_ERR_CALLBACK_FAILURE
  2. 若只用nghttp2_session_mem_send()/nghttp2_session_mem_send2(),可以完全不注册该回调;
  3. 该 API 已弃用,新代码应使用nghttp2_session_callbacks_set_send_callback2()+nghttp2_send_callback2
  4. 注意length可能很小,应用应自行缓冲小块数据以规避 Nagle 算法禁用时的小报文问题。

如需进一步阅读仓库内相关实现与资料,可参考:

  • 官方文档: nghttp2_session_callbacks_set_send_callback.rst、nghttp2_session_callbacks_set_send_callback2.rst
  • 头文件定义: nghttp2.h
  • 回调实现: nghttp2_callbacks.c
  • 官方示例: examples/client.c
  • Fluent Bit 集成实现: src/flb_http_client_http2.c、src/http_server/flb_http_server_http2.c

【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询