news 2026/9/17 6:32:20

nghttp2 扩展帧接收注册函数 nghttp2_option_set_user_recv_extension_type 全面解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
nghttp2 扩展帧接收注册函数 nghttp2_option_set_user_recv_extension_type 全面解析

nghttp2 扩展帧接收注册函数 nghttp2_option_set_user_recv_extension_type 全面解析

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

本指南以 nghttp2-1.65.0(内嵌于当前 fluent-bit 仓库lib/nghttp2-1.65.0/)中的 nghttp2_option_set_user_recv_extension_type.rst 为骨架,围绕nghttp2_option_set_user_recv_extension_type()这一个 API 展开,讲解它如何向 nghttp2 会话注册"应用程序愿意通过自定义回调接收的 HTTP/2 扩展帧类型",并结合库内实现(nghttp2_option.cnghttp2_session.c)、头文件声明、程序员指南示例与单元测试,带你掌握扩展帧接收的完整调用链与底层原理。读完本文,你将能够在自己的 nghttp2 客户端/服务端程序中正确注册、接收并解包任意自定义 HTTP/2 扩展帧。

函数签名与核心语义

该 API 的原型定义在公共头文件 nghttp2.h 中:

#include <nghttp2/nghttp2.h> void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, uint8_t type);

其作用(原文语义)是:设置应用程序愿意通过用户自定义回调处理的扩展帧类型。这里的"用户自定义回调"特指两个回调:

  • nghttp2_on_extension_chunk_recv_callback——负责逐段缓冲(buffering)到达的扩展帧载荷;
  • nghttp2_unpack_extension_callback——负责把缓冲好的 wire format 载荷解包(unpack)为应用程序可用的结构。

二者在 nghttp2.h 中定义,完整语义见 types.rst。

核心语义要点如下:

  1. type是扩展帧类型号,取值范围必须严格大于0x9(十进制 9);
  2. 若传入的type不满足上述条件,该函数什么都不做(静默忽略,不报错);
  3. 可多次调用,为同一个nghttp2_option注册多个扩展帧类型,全部生效;
  4. 纯发送场景无需调用——如果应用只是通过nghttp2_submit_extension()发送扩展帧、并不打算接收对端发来的扩展帧,则完全不需要调用此函数。

为什么必须严格大于 0x9:HTTP/2 帧类型空间

HTTP/2 规范(RFC 9113)在帧头(9 字节固定头部)的第 3 字节编码帧类型(8 位),并预定义了 0x0~0x9 共 10 种标准帧:

类型值帧名
0x0DATA
0x1HEADERS
0x2PRIORITY
0x3RST_STREAM
0x4SETTINGS
0x5PUSH_PROMISE
0x6PING
0x7GOAWAY
0x8WINDOW_UPDATE
0x9CONTINUATION

因此0xa(10)及以上的类型号才属于扩展帧类型(如NGHTTP2_ALTSVC即为0xa)。nghttp2_option_set_user_recv_extension_type()的参数检查正是基于这一规范约定:type若落在标准帧区间内,注册无意义,函数直接返回。

底层实现剖析:位图式类型集合

该函数的实现在 nghttp2_option.c:

static void set_ext_type(uint8_t *ext_types, uint8_t type) { ext_types[type / 8] = (uint8_t)(ext_types[type / 8] | (1 << (type & 0x7))); } void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, uint8_t type) { if (type < 10) { return; } option->opt_set_mask |= NGHTTP2_OPT_USER_RECV_EXT_TYPES; set_ext_type(option->user_recv_ext_types, type); }

从源码可以看到三个关键设计:

  1. 越界静默返回if (type < 10) return;与文档中 "must be strictly greater than 0x9. Otherwise, this function does nothing" 完全对应。
  2. 位图存储nghttp2_option结构体内定义了一个uint8_t user_recv_ext_types[32]数组(见 nghttp2_option.h),共 32×8 = 256 位,恰好覆盖 8 位帧类型号 0~255 的全部取值空间。set_ext_type通过type / 8定位字节、1 << (type & 0x7)定位位,把对应帧类型标记进位图。由于是"按位或"写入,多次调用天然合并,无需担心覆盖。
  3. 掩码标记:设置opt_set_mask |= NGHTTP2_OPT_USER_RECV_EXT_TYPES,用于让会话创建逻辑知道该选项已被显式设置,从而把位图拷贝进会话。

从 option 到 session:类型集合如何生效

nghttp2_option只是配置载体,真正在收帧时起作用的是会话内部拷贝的位图。在 nghttp2_session.c 中,创建会话(如nghttp2_session_client_new2)时会把选项位图整体拷贝到会话:

memcpy((*session_ptr)->user_recv_ext_types, option->user_recv_ext_types, sizeof((*session_ptr)->user_recv_ext_types));

随后,库在帧释放与分发路径上通过check_ext_type_set(session->user_recv_ext_types, frame->hd.type)判断某帧是否属于应用注册的自定义扩展帧类型(见 nghttp2_session.c 及 L5722 附近的收帧路径)。这解释了为什么必须在创建会话之前就把扩展帧类型注册进 option,并用该 option 创建会话——例如:

nghttp2_session_client_new2(&session, callbacks, user_data, option);

接收扩展帧的完整调用链与示例

注册与回调装配

按 programmers-guide.rst 的完整示例,接收扩展帧需要四步:

  1. 实现on_extension_chunk_recv_callback:把到达的载荷逐段缓冲到应用缓冲区;
  2. 实现unpack_extension_callback:把缓冲内容按自定义格式解包,可把结果对象存入*payload(库不持有该指针,应用需自行管理内存);
  3. 通过nghttp2_session_callbacks_set_on_extension_chunk_recv_callback()nghttp2_session_callbacks_set_unpack_extension_callback()装配两个回调;
  4. 调用nghttp2_option_set_user_recv_extension_type()注册帧类型,并用该 option 创建会话。

以 ALTSVC(帧类型0xa)为例,两个回调的典型实现如下(来自程序员指南,略作整理):

typedef struct { const uint8_t *origin; size_t originlen; const uint8_t *field; size_t fieldlen; } alt_svc; /* buffers incoming ALTSVC payload */ uint8_t altsvc_buffer[4096]; size_t altsvc_bufferlen = 0; int on_extension_chunk_recv_callback(nghttp2_session *session, const nghttp2_frame_hd *hd, const uint8_t *data, size_t len, void *user_data) { if (sizeof(altsvc_buffer) < altsvc_bufferlen + len) { altsvc_bufferlen = 0; return NGHTTP2_ERR_CANCEL; } memcpy(altsvc_buffer + altsvc_bufferlen, data, len); altsvc_bufferlen += len; return 0; } int unpack_extension_callback(nghttp2_session *session, void **payload, const nghttp2_frame_hd *hd, void *user_data) { /* ALTSVC wire format: 2-byte origin length + origin + field-value */ uint8_t *p = altsvc_buffer, *end = altsvc_buffer + altsvc_bufferlen; size_t originlen = ((*p) << 8) + *(p + 1); p += 2; if (p + originlen > end) { altsvc_bufferlen = 0; return NGHTTP2_ERR_CANCEL; } alt_svc *altsvc = (alt_svc *)malloc(sizeof(alt_svc)); altsvc->origin = p; altsvc->originlen = originlen; altsvc->field = p + originlen; altsvc->fieldlen = end - (p + originlen); *payload = altsvc; /* 解包结果存入 *payload */ altsvc_bufferlen = 0; return 0; }

装配回调并注册帧类型:

nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( callbacks, on_extension_chunk_recv_callback); nghttp2_session_callbacks_set_unpack_extension_callback( callbacks, unpack_extension_callback); nghttp2_option_set_user_recv_extension_type(option, 0xa); nghttp2_session_client_new2(&session, callbacks, user_data, option);

在 on_frame_recv_callback 中消费

解包完成后,库会照常调用nghttp2_on_frame_recv_callback,此时*payload可通过frame->ext.payload取得,应用可在该回调中读取数据并释放内存:

int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { switch (frame->hd.type) { case 0xa: { alt_svc *altsvc = (alt_svc *)frame->ext.payload; fprintf(stderr, "ALTSVC frame received\n"); fprintf(stderr, " origin: %.*s\n", (int)altsvc->originlen, altsvc->origin); fprintf(stderr, " field : %.*s\n", (int)altsvc->fieldlen, altsvc->field); free(altsvc); break; } } return 0; }

错误处理约定

两个回调的返回值约定一致(见 nghttp2.h):

  • 返回0表示成功;
  • 返回NGHTTP2_ERR_CANCEL表示中止处理该扩展帧(nghttp2_on_frame_recv_callback将不再被调用);
  • 返回NGHTTP2_ERR_CALLBACK_FAILURE表示致命错误,nghttp2_session_recv()nghttp2_session_mem_recv2()会立即返回该错误码;其他非零返回值目前一律按NGHTTP2_ERR_CALLBACK_FAILURE处理。

与内置接收(builtin)类型的区别与优先级

nghttp2 另提供nghttp2_option_set_builtin_recv_extension_type(),用于接收库内置处理器支持的扩展帧,目前包括 ALTSVC、ORIGIN 与 PRIORITY_UPDATE(见 nghttp2_option.c 的 switch 实现)。两者的差异见 nghttp2_option_set_builtin_recv_extension_type.rst:

对比维度user_recv_extension_typebuiltin_recv_extension_type
处理方式用户自定义回调(chunk + unpack)库内置 handler
支持的帧类型任意> 0x9的类型号(256 位位图)仅 ALTSVC、ORIGIN、PRIORITY_UPDATE
适用场景自定义私有扩展帧标准扩展帧
优先级同一类型同时注册时,user 版本优先被 user 版本覆盖

关键点:如果同一帧类型同时通过两个函数注册,后者(user 版本)优先生效——这是文档与 programmers-guide.rst 中明确声明、并在会话内部check_ext_type_set优先于builtin_recv_ext_types判断中体现的设计(见 nghttp2_session.c)。这意味着即使 nghttp2 内置了某扩展帧的处理,你仍可"接管"该帧类型实现自己的 handler。

单元测试验证

仓库的会话测试 nghttp2_session_test.c 中的test_nghttp2_session_recv_extension直接覆盖了本 API 的行为:

nghttp2_option_new(&option); nghttp2_option_set_user_recv_extension_type(option, 111); ... nghttp2_session_client_new2(&session, &callbacks, &ud, option);

测试构造了一个帧类型为111(0x6f)、带任意 flags 与 stream_id 的扩展帧,通过nghttp2_session_mem_recv2()送入会话,随后断言:接收字节数正确、on_frame_recv_callback收到的hd.type == 111、flags 与 stream_id 原样透传、载荷 "Hello World!" 完整进入缓冲。测试还分别验证了在on_extension_chunk_recv_callbackunpack_extension_callback中返回NGHTTP2_ERR_CANCEL时,on_frame_recv_callback不再被调用(frame_recv_cb_called == 0)。这三条路径共同印证了本文描述的完整调用链。

使用要点与注意事项

  1. 必须在创建会话前注册:位图在nghttp2_session_client_new2()/nghttp2_session_server_new2()时从 option 拷贝,事后修改 option 不影响已创建的会话;会话生命周期内如需变更接收类型,需重建会话。
  2. 同一 option 可复用:一个nghttp2_option可注册多个类型(多次调用),也可同时传给多个会话。
  3. type 参数是uint8_t:帧类型号为 8 位,合法范围是 10~255(0xa~0xff);传入 0~9 会被静默忽略,建议在调用前自行校验避免误解。
  4. 发送不受影响:仅发送扩展帧时无需任何注册;只有接收路径才需要调用本函数。
  5. 内存管理责任在应用unpack_extension_callback写入*payload的指针由库透传给on_frame_recv_callbackframe->ext.payload,库不负责释放,应用需在消费后自行free;也可完全不使用*payload,采用自己的机制处理。
  6. 与内置类型的覆盖关系:若希望覆盖内置类型(如 ALTSVC)的处理,用本函数注册相同类型号即可,user 回调优先。
  7. 缓冲区需防御性检查on_extension_chunk_recv_callback可能被多次调用以传输完整载荷,需自行累计长度并做越界保护(参考示例中的sizeof(altsvc_buffer) < altsvc_bufferlen + len检查),越界时返回NGHTTP2_ERR_CANCEL中止处理。

参考文档索引

  • 本函数 API 文档:nghttp2_option_set_user_recv_extension_type.rst
  • 内置接收对照文档:nghttp2_option_set_builtin_recv_extension_type.rst
  • 头文件声明与回调定义:nghttp2.h
  • 完整收发示例(程序员指南):programmers-guide.rst
  • 回调类型语义说明:types.rst
  • 实现源码:nghttp2_option.c、nghttp2_session.c
  • 单元测试:nghttp2_session_test.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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 6:30:19

小爱音箱放本地音乐的完整指南:xiaomusic 从安装到开口点播

小爱音箱放本地音乐的完整指南&#xff1a;xiaomusic 从安装到开口点播 【免费下载链接】xiaomusic 使用小爱音箱播放音乐&#xff0c;音乐使用 yt-dlp 下载。 项目地址: https://gitcode.com/GitHub_Trending/xia/xiaomusic 周六早晨你随口一句"小爱同学&#xff…

作者头像 李华
网站建设 2026/9/17 6:29:53

Modbus TCP调试避坑指南:IP、Unit ID、Server/Client一个都不能错

做自动化这些年&#xff0c;被问到最多的问题之一就是&#xff1a;Modbus TCP参数看着都对&#xff0c;为啥就是不行&#xff1f;说实话&#xff0c;我也在这个坑里摔过不少次。尤其现在PLC、HMI、上位机、第三方板卡到处都要走Modbus TCP&#xff0c;明明协议是公开的、报文结…

作者头像 李华
网站建设 2026/9/17 6:28:52

PyCharm+MicroPython开发环境搭建实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 6:26:25

拆解99.75%成功率的通达信副图:CCI、RSI、KDJ三线共振选股

简介&#xff1a;通达信 99.75% 成功率指标公式源码以 doc 文档收录&#xff0c;面向借助通达信复盘与自编指标的股票交易者、量化入门学习者&#xff0c;用于解决指标逻辑难复现、买卖信号条件不清晰的问题。文档给出完整公式源码&#xff0c;可看到 TYP 典型价与 AVEDEV 构建…

作者头像 李华
网站建设 2026/9/17 6:25:34

HCL模拟器网络联调实战:虚拟网卡、Cloud云与VMware互通全解析

前几天一个学网络工程的学员给我发消息&#xff0c;说他用华三HCL模拟器搭了一个MSR路由器加两台PC的拓扑&#xff0c;想试试真实场景里的NAT和防火墙策略&#xff0c;结果设备启动都正常&#xff0c;就是怎么都ping不通外网。我让他先查虚拟网卡&#xff0c;他说设备管理器里只…

作者头像 李华