nghttp2 帧接收回调详解:nghttp2_session_callbacks_set_on_frame_recv_callback 的注册、触发与工程实践 nghttp2 帧接收回调详解nghttp2_session_callbacks_set_on_frame_recv_callback 的注册、触发与工程实践【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit导读本文聚焦 nghttp2HTTP/2 C 语言库会话回调体系中的一个核心入口nghttp2_session_callbacks_set_on_frame_recv_callback。它负责向会话回调集合注册帧接收完成回调是 HTTP/2 客户端与服务器感知对端帧到达、推进流状态机的主通道。文章将依次讲解该函数的原型与头文件位置、回调函数签名的语义、触发时机与返回值约定并结合本仓库中 fluent-bit 的 HTTP/2 客户端实现src/flb_http_client_http2.c以及 nghttp2 自带的测试服务器lib/nghttp2-1.65.0/src/HttpServer.cc给出可运行的注册代码与真实应用模式读完即可在自己的 nghttp2 会话中正确接入帧接收处理逻辑。一、函数定位与原型该函数是 nghttp2 会话回调nghttp2_session_callbacks的一族setter之一作用是把用户实现的帧接收回调写入回调集合对象供后续创建的会话在收到帧时调用。其权威文档位于仓库内的 lib/nghttp2-1.65.0/doc/nghttp2_session_callbacks_set_on_frame_recv_callback.rst原型如下#include nghttp2/nghttp2.h void nghttp2_session_callbacks_set_on_frame_recv_callback( nghttp2_session_callbacks *cbs, nghttp2_on_frame_recv_callback on_frame_recv_callback);从源码看这一 setter 的实现非常直接就是把函数指针存进回调结构体对应的成员void nghttp2_session_callbacks_set_on_frame_recv_callback( nghttp2_session_callbacks *cbs, nghttp2_on_frame_recv_callback on_frame_recv_callback) { cbs-on_frame_recv_callback on_frame_recv_callback; }见 lib/nghttp2-1.65.0/lib/nghttp2_callbacks.c#L63-L67与send_callback、recv_callback、on_data_chunk_recv_callback等 setter 一起这些函数组成了 nghttp2 的注册回调 → 创建会话 → 会话驱动回调的完整编程模型全部声明集中在头文件 lib/nghttp2-1.65.0/lib/includes/nghttp2/nghttp2.h 中。二、回调函数签名三个参数各自的语义本函数注册的回调类型nghttp2_on_frame_recv_callback定义于 nghttp2.h#L1681-L1683typedef int (*nghttp2_on_frame_recv_callback)(nghttp2_session *session, const nghttp2_frame *frame, void *user_data);参数类型语义sessionnghttp2_session *触发本次回调的 nghttp2 会话对象可用于查询流状态、提交帧等frameconst nghttp2_frame *已完整接收并校验通过的帧描述结构只读内含帧头与类型化载荷user_datavoid *创建会话时传入的第三个参数nghttp2_session_client_new()/nghttp2_session_server_new()通常指向应用上下文frame参数的结构体nghttp2_frame是一个通用帧头 类型化联合体的设计通用的 9 字节 HTTP/2 帧头统一放在frame-hd中通过frame-hd.type帧类型、frame-hd.flags标志位、frame-hd.stream_id流 ID即可判断是哪种帧而frame-headers.cat如NGHTTP2_HCAT_REQUEST、frame-headers.nva/nvlen头字段数组与数量等则按帧类型提供细分信息。例如 nghttp2 自带的测试服务器在回调里就用switch (frame-hd.type)区分NGHTTP2_DATA、NGHTTP2_HEADERS、NGHTTP2_SETTINGS等分支处理见 lib/nghttp2-1.65.0/src/HttpServer.cc#L1506-L1564。三、触发时机与单帧语义根据 关联文档 与头文件注释该回调由nghttp2_session_recv()与nghttp2_session_mem_recv2()在一个帧被完整接收之后调用。这意味着它不属于逐字节处理的recv_callback那是原始的接收回调负责把数据喂给库而是库内部完成帧解析、校验之后的上层业务回调回调触发时帧已经过完整性校验用户可以直接信任frame中的字段HEADERS / PUSH_PROMISE 与其后续的 CONTINUATION 帧会被当作一个整体single frame处理即只有全部头部片段收齐后该回调才会以合并后的帧触发一次用户无需自行拼接 CONTINUATION。正因如此nghttp2_on_frame_recv_callback是感知某个流收到了完整的 HEADERS或整个流收到了 END_STREAM 结束帧的最可靠位置。头文件注释也专门提示DATA 帧的负载分片回调nghttp2_on_data_chunk_recv_callback中即使看到NGHTTP2_FLAG_END_STREAM标志也不代表数据一定收完应当使用on_frame_recv_callback来确认所有 DATA 帧都已接收见 nghttp2.h#L1722-L1724 附近注释。四、返回值约定0 成功非 0 即致命错误回调的返回值语义在 nghttp2.h#L1672-L1676 有明确约定返回 0处理成功会话继续解析后续输入返回任意非 0 值被当作致命错误nghttp2_session_recv()与nghttp2_session_mem_recv2()会立即返回NGHTTP2_ERR_CALLBACK_FAILURE并停止继续消费输入。因此回调实现必须小心处理所有分支路径确保正常情况下返回 0。以 fluent-bit 的实现为例即使遇到未知的流stream NULL也要先return 0放行避免因找不到流上下文而误杀整个会话见 src/flb_http_client_http2.c#L271-L276。五、最小可运行的注册与使用示例回调的注册必须与创建回调集合 → 创建会话 → 喂数据配合完整的最小流程如下基于 nghttp2 官方教程的惯用写法教程位于 lib/nghttp2-1.65.0/doc/sources/tutorial-server.rst 与 lib/nghttp2-1.65.0/doc/sources/tutorial-client.rst#include nghttp2/nghttp2.h /* 1. 实现帧接收回调 */ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { /* user_data 为创建会话时传入的应用上下文 */ switch (frame-hd.type) { case NGHTTP2_HEADERS: if (frame-hd.flags NGHTTP2_FLAG_END_STREAM) { /* 该流头部收齐且对端已结束发送 */ } break; case NGHTTP2_DATA: /* DATA 帧到达负载分片见 on_data_chunk_recv_callback */ break; case NGHTTP2_SETTINGS: if (frame-hd.flags NGHTTP2_FLAG_ACK) { /* 收到 SETTINGS ACK */ } break; default: break; } return 0; /* 必须返回 0否则会话以 NGHTTP2_ERR_CALLBACK_FAILURE 中止 */ } /* 2. 创建回调集合并注册 */ nghttp2_session_callbacks *cbs; nghttp2_session_callbacks_new(cbs); nghttp2_session_callbacks_set_recv_callback(cbs, recv_cb); nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv); /* 3. 创建会话客户端或服务器user_data 即回调的第三个参数 */ nghttp2_session_server_new(session, cbs, /* user_data */ app_ctx); /* 4. 循环调用 nghttp2_session_mem_recv2() / nghttp2_session_recv() 喂入数据 库在完整收到一个帧后自动回调 on_frame_recv */ nghttp2_session_callbacks_del(cbs); /* 会话创建后可释放回调集合 */注意nghttp2_session_callbacks_new负责分配回调集合对象见 lib/nghttp2-1.65.0/lib/nghttp2_callbacks.c#L29-L37注册完、会话创建完成后即可用nghttp2_session_callbacks_del释放库内部已持有所需函数指针。六、仓库实战fluent-bit HTTP/2 客户端如何用它驱动流状态机fluent-bit 将 nghttp2 作为内置的 HTTP/2 实现第三方库位于 lib/nghttp2-1.65.0在 src/flb_http_client_http2.c 中通过本函数注册了http2_frame_recv_callbacknghttp2_session_callbacks_set_on_frame_recv_callback( callbacks, http2_frame_recv_callback);见 src/flb_http_client_http2.c#L488该回调的实现src/flb_http_client_http2.c#L264-L317是一个典型的工程化用法可以提炼出三个可复用的设计模式用流 ID 反查应用上下文通过nghttp2_session_get_stream_user_data(inner_session, frame-hd.stream_id)拿到之前提交请求时挂载的flb_http_stream把库的流 ID 映射回业务对象查不到时直接return 0跳过保证健壮性。用帧类型 标志位推进状态机对NGHTTP2_HEADERS/NGHTTP2_CONTINUATION依据NGHTTP2_FLAG_END_HEADERS把流状态从接收头部推进到接收数据无该标志则转入接收尾部trailer状态——这正好呼应上文HEADERS 与 CONTINUATION 合并为一个帧的语义。用 END_STREAM 做收尾与移交当frame-hd.flags NGHTTP2_FLAG_END_STREAM时把流状态置为HTTP_STREAM_STATUS_READY并将该流的响应从孤儿链表摘下、挂入父会话的response_queue响应队列交给上层消费。同样的回调注册模式也出现在 fluent-bit 的 HTTP/2 服务器侧实现 src/http_server/flb_http_server_http2.c 中说明帧接收回调是 nghttp2 客户端与服务器两条路径共用的核心处理点。此外nghttp2 仓库自带的示例 lib/nghttp2-1.65.0/examples/libevent-server.c 与 lib/nghttp2-1.65.0/examples/client.c 也大量使用了该回调可对照阅读。七、与相邻回调的分工与配合on_frame_recv_callback并非孤立存在理解它与兄弟回调的分工才能正确划分业务逻辑回调setter触发时机与本文回调的关系nghttp2_on_recv_callbacknghttp2_session_callbacks_set_recv_callback原始数据到达逐块喂给库更底层负责向库提供字节nghttp2_on_data_chunk_recv_callbacknghttp2_session_callbacks_set_on_data_chunk_recv_callbackDATA 帧的每块负载处理负载分片流结束与否以本文回调为准nghttp2_on_invalid_frame_recv_callbacknghttp2_session_callbacks_set_on_invalid_frame_recv_callback收到非法非 DATA 帧错误路径库会自动提交 RST_STREAM 或 GOAWAYnghttp2_on_frame_send_callbacknghttp2_session_callbacks_set_on_frame_send_callback帧发送完成发送方向的对偶回调nghttp2_on_stream_close_callbacknghttp2_session_callbacks_set_on_stream_close_callback流关闭负责释放流级资源其中两点特别值得注意均出自头文件注释nghttp2.h#L1688-L1709 等非法帧回调触发时若帧为 HEADERS 或 PUSH_PROMISE其nva/nvlen恒为NULL和 0头部解析失败时内容不可信而合法帧的头部解析成功保证正是本文回调可以安全读取frame-headers.nva的前提若应用通过nghttp2_session_mem_recv2()接收数据并在on_data_chunk_recv_callback中返回NGHTTP2_ERR_PAUSE暂停相关输入内存必须保留到下次调用这也是编排各回调时需要注意的生命周期约束。八、小结nghttp2_session_callbacks_set_on_frame_recv_callback虽然只是一个赋值函数但它是 nghttp2 接收路径上协议解析完成 → 业务可处理的关键衔接点。掌握它的注册方式、nghttp2_on_frame_recv_callback的签名语义、HEADERS/CONTINUATION 合并触发的单帧约定、以及非 0 即致命的返回值规则再结合 fluent-bit 在 src/flb_http_client_http2.c 中按流反查上下文 标志位驱动状态机 END_STREAM 收尾移交的实战范式即可在自有 HTTP/2 客户端或服务器中稳妥地处理帧事件。进一步的权威说明可继续阅读 关联 API 文档 与 lib/nghttp2-1.65.0/doc/types.rst。【免费下载链接】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),仅供参考