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

Fluent Bit 与 nghttp2 中的 nghttp2_session_callbacks_set_send_callback 指南:HTTP/2 发送回调的设置、语义与工程实践 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-bitFluent 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.cvoid 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_callback与send_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.htypedef 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当前未使用恒为 0user_data创建会话时nghttp2_session_client_new()或nghttp2_session_server_new()传入的第三个参数通常用于携带连接上下文。3.2 弃用警告与替代方案关联文档明确标注该 API 为Deprecated已弃用建议改用nghttp2_session_callbacks_set_send_callback2()setternghttp2_send_callback2回调类型nghttp2_session_mem_send2()配套的内存序列化发送函数。新版回调类型nghttp2_send_callback2与旧版唯一的差异是返回类型从ssize_t换成了nghttp2_ssize库自定义的符号类型参数列表完全一致定义见 lib/nghttp2-1.65.0/lib/includes/nghttp2/nghttp2.htypedef 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; }这段代码展示了三个关键工程点通过user_data取回连接上下文这里是struct Connection *并先清除 I/O 等待状态SSL 层的SSL_ERROR_WANT_WRITE/SSL_ERROR_WANT_READ一律映射为NGHTTP2_ERR_WOULDBLOCK让 nghttp2 知道需要等待事件循环重新调度其余错误统一映射为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.csrc/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它把会话想发数据这一事件与应用实际写出字节这一动作连接起来。核心要点回顾回调必须返回实际发送字节数或在无法发送时返回NGHTTP2_ERR_WOULDBLOCK其他错误返回NGHTTP2_ERR_CALLBACK_FAILURE若只用nghttp2_session_mem_send()/nghttp2_session_mem_send2()可以完全不注册该回调该 API 已弃用新代码应使用nghttp2_session_callbacks_set_send_callback2()nghttp2_send_callback2注意length可能很小应用应自行缓冲小块数据以规避 Nagle 算法禁用时的小报文问题。如需进一步阅读仓库内相关实现与资料可参考官方文档 nghttp2_session_callbacks_set_send_callback.rst、nghttp2_session_callbacks_set_send_callback2.rst头文件定义 nghttp2.h回调实现 nghttp2_callbacks.c官方示例 examples/client.cFluent 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),仅供参考