ngx_chain_update_sent
1 定义ngx_chain_update_sent 函数 定义在 src/core/ngx_buf.cngx_chain_t*ngx_chain_update_sent(ngx_chain_t*in,off_tsent){off_tsize;for(/* void */;in;inin-next){if(ngx_buf_special(in-buf)){continue;}if(sent0){break;}sizengx_buf_size(in-buf);if(sentsize){sent-size;if(ngx_buf_in_memory(in-buf)){in-buf-posin-buf-last;}if(in-buf-in_file){in-buf-file_posin-buf-file_last;}continue;}if(ngx_buf_in_memory(in-buf)){in-buf-pos(size_t)sent;}if(in-buf-in_file){in-buf-file_possent;}break;}returnin;}2 目的1 设计意图ngx_chain_update_sent根据已发送字节数sent推进缓冲链中各缓冲区的读写位置指针并返回尚未完全发送的第一个缓冲区所在的链节点。它是 Nginx 网络发送路径上的一个簿记bookkeeping辅助函数——不执行任何网络 I/O只负责更新缓冲区状态使上层可以继续从正确的位置重试发送。3 详解1 函数签名ngx_chain_t*ngx_chain_update_sent(ngx_chain_t*in,off_tsent);1 返回值ngx_chain_t *返回值含义NULL链中所有缓冲区数据已全部发送完毕或被跳过无需重试! NULL返回的链节点是第一个尚有未发送数据的缓冲区所在位置上层应从此处继续发送2 函数名ngx_chain_update_sent词段含义ngx_Nginx 命名前缀chain作用对象缓冲区链ngx_chain_tupdate动作更新/推进链中各缓冲区的内部指针sent依据以已发送字节数为输入3 参数列表参数名类型含义来源约束inngx_chain_t *待更新状态的缓冲区链头节点发送函数如ngx_writev_chain传入可为 NULL表示空链直接返回 NULLsentoff_t本次已成功发送的总字节数系统调用返回值sendfile()/writev()/sendmsg()等sent 0若为 0 表示未发送任何数据可能是EAGAIN2 逻辑流程ngx_chain_update_sent(in, sent) ├─ [0] 空链检测 │ └─ in NULL → 直接返回 NULL └─ 遍历链节点 (for in; in in-next) ├─ [1] 特殊缓冲区跳过 │ └─ ngx_buf_special(in-buf) true → continue (不消耗 sent) ├─ [2] 零发送检测 │ └─ sent 0 → break (返回当前节点) ├─ [3] 完全消费 │ └─ sent ngx_buf_size(in-buf) → sent 减去本 buf 大小 │ ├─ [3.1] 内存缓冲区pos last │ └─ [3.2] 文件缓冲区file_pos file_last │ → continue (本 buf 已空继续下一个) └─ [4] 部分消费 └─ sent ngx_buf_size(in-buf) ├─ [4.1] 内存缓冲区pos sent └─ [4.2] 文件缓冲区file_pos sent → break (当前 buf 部分剩余返回此节点){off_tsize;}局部变量声明局部变量size用于在循环体内保存当前节点的ngx_buf_size(in-buf)计算结果避免在完全消费分支的条件判断和减法中重复计算。遍历for(/* void */;in;inin-next){进入条件in非 NULL空链在循环体入口就被自然跳过但见下方 [0] 分支说明。处理逻辑标准的单链表遍历模式。for的三个子句中省略了初始化子句/* void */即空语句条件为in当链遍历到末尾时为 NULL循环终止步进为in in-next。设计意图函数需要从头到尾扫描链找到已发送字节数恰好耗尽时所在的那个缓冲区。每轮循环检查当前节点对应缓冲区的剩余大小与sent比较决定是否继续前进。0 空链检测虽未显式出现在源码中但for (; in; ...)的条件in天然处理了空链当in NULL时循环体一次也不执行直接执行return in即返回 NULL。调用方收到 NULL 后知道没有剩余数据需要发送可以结束本次发送循环。1 特殊缓冲区跳过if(ngx_buf_special(in-buf)){continue;}进入条件ngx_buf_special(in-buf)为 true。简言之这是一个只有控制标记、没有任何实际数据的信号缓冲区。处理逻辑continue直接跳到下一个链节点不改变sent的值。设计意图这些特殊缓冲区是在链中插入的控制信号用于告知下游有特殊处理需求如flush表示需要立即刷新、last_buf表示这是最后一个数据缓冲区、sync表示同步点。它们没有实际数据需要发送因此不算入已发送字节数的消耗。2 零发送检测if(sent0){break;}进入条件sent 0即系统调用未发送任何字节。处理逻辑break跳出循环返回当前节点。设计意图sent 0通常意味着系统调用返回了EAGAIN套接字发送缓冲区已满暂无空间可写。此时没有数据被消费当前节点及其后续所有节点的缓冲区状态都不需要改变。返回当前节点调用方会将其保存起来等待下次可写事件时从此处继续发送。边界分析此检查放在ngx_buf_special检查之后——如果第一个节点是特殊缓冲区会被跳过然后检查sent 0。这样确保不会因为特殊缓冲区的存在而错误返回特殊缓冲区节点特殊缓冲区没有数据不应该作为发送起点返回给上层。3 完全消费sizengx_buf_size(in-buf);if(sentsize){sent-size;if(ngx_buf_in_memory(in-buf)){in-buf-posin-buf-last;}if(in-buf-in_file){in-buf-file_posin-buf-file_last;}continue;}进入条件sent size即已发送字节数 ≥ 当前缓冲区的剩余大小。当前缓冲区的所有数据都已成功发送。处理逻辑sent - size从已发送总字节数中减去当前缓冲区的剩余大小。这样sent实际上表示还剩多少已发送字节数未映射到具体的缓冲区位置。内存缓冲区清空[3.1]如果缓冲区在内存中temporary || memory || mmap将pos设置为last表示已消费完当前缓冲区的所有内存数据。后续再次取ngx_buf_size时将返回 0。文件缓冲区清空[3.2]如果缓冲区关联到文件in_file为 1将file_pos设置为file_last表示文件部分也已发送完毕。continue进入下一轮循环处理链中的下一个节点。设计意图零拷贝语义对于内存缓冲区pos last只是移动了指针并没有释放或修改数据本身。pos和last之间的区域表示待发送数据将pos推到last即表示数据已发送。底层内存仍在start到end之间可以被内存池回收复用。对于文件缓冲区file_pos file_last同样只是移动了偏移量位置。一个缓冲区可能同时存在于内存和文件如果一个缓冲区同时设置了内存标记temporary等和in_file两个 if 条件都会进入——内存指针和文件偏移同时被推进。这种情况出现在 Nginx 需要同时追踪内存数据和文件映射数据时。continue而非break完全消费后继续处理下一个节点因为sent可能足以覆盖多个缓冲区。4 部分消费if(ngx_buf_in_memory(in-buf)){in-buf-pos(size_t)sent;}if(in-buf-in_file){in-buf-file_possent;}break;进入条件sent size隐含因为前面的sent size条件未满足即落入此分支。已发送字节数不足以覆盖当前缓冲区的全部剩余数据当前缓冲区只有部分数据被发送。处理逻辑内存缓冲区部分推进[4.1]pos (size_t) sent将读取指针前进sent字节表示这部分内存数据已发送。使用(size_t)强制转换是因为pos是u_char *sent是off_t有符号 64 位整数需要转为size_t后才能安全地与指针运算。文件缓冲区部分推进[4.2]file_pos sent将文件偏移前进sent字节。break跳出循环。sent已全部用完当前节点是第一个还有剩余数据的节点。返回此节点调用方将在下一次发送时从此处继续。设计意图部分发送的簿记核心这是该函数最重要的分支。网络 I/O 系统调用sendfile、writev、sendmsg经常出现部分发送——例如缓冲区有 4096 字节实际只发送了 1500 字节。发送函数需要记录这 1500 字节已被消费下次从此位置继续。break非continue与 [3] 不同此处break是因为sent已耗尽不能再前进到下一个节点。边界与安全分析进入此分支时sent 0必然成立因为 [2] 已经在前面过滤了sent 0的情况。若sent恰好等于size应由 [3] 分支处理sent size为真不会落入此分支。两个分支是互斥的。(size_t) sent转换是安全的因为pos和last是现代 64 位平台上地址空间内的指针且缓冲区大小通常不会超过SIZE_MAX在 ILP32 平台上size_t是 32 位但 Nginx 的缓冲区通常远小于 4GB。

相关新闻