当前位置: 首页 > news >正文

ngx_writev

1 定义

ngx_writev 函数 定义在 src/os/unix/ngx_writev_chain.c
ssize_tngx_writev(ngx_connection_t*c,ngx_iovec_t*vec){ssize_tn;ngx_err_terr;eintr:n=writev(c->fd,vec->iovs,vec->count);ngx_log_debug2(NGX_LOG_DEBUG_EVENT,c->log,0,"writev: %z of %uz",n,vec->size);if(n==-1){err=ngx_errno;switch(err){caseNGX_EAGAIN:ngx_log_debug0(NGX_LOG_DEBUG_EVENT,c->log,err,"writev() not ready");returnNGX_AGAIN;caseNGX_EINTR:ngx_log_debug0(NGX_LOG_DEBUG_EVENT,c->log,err,"writev() was interrupted");gotoeintr;default:c->write->error=1;ngx_connection_error(c,err,"writev() failed");returnNGX_ERROR;}}returnn;}

2 目的

1 设计意图

ngx_writev是 Nginx 对 POSIXwritev(2)系统调用的薄封装函数
负责将一组struct iovec数组
一次性写入到 TCP 套接字文件描述符。
它是发送链路的最底层——直接与内核交互的系统调用入口。

2 上下游关系

ngx_writev_chain (发送链管理) │ 将 ngx_chain_t 链表转为 iovec 数组 │ 调用 ngx_writev 执行实际发送 ▼ ngx_writev (本函数) │ 1. 调用 writev(2) 系统调用 │ 2. 将 POSIX errno 映射为 Nginx 内部错误码 │ 3. 处理 EAGAIN / EINTR / 其他错误 ▼ writev() 系统调用 ──► 内核 TCP 协议栈

核心职责拆分:

  • 系统调用适配
    将 Nginx 的ngx_iovec_t结构转换为 POSIXwritev的参数格式(vec->iovsvec->count)。
  • 错误码归一化
    将 POSIX 的errnoEAGAINEINTR等)映射为 Nginx 内部统一的错误码
    NGX_AGAINNGX_ERROR),
    使上层调用者不直接依赖errno
  • EINTR 安全重试:对信号中断(EINTR)自动重试,保证信号安全语义。
  • 调试日志:在每次系统调用后记录实际发送字节数与预期量的关系,便于问题排查。

3 详解

1 函数签名

ssize_tngx_writev(ngx_connection_t*c,ngx_iovec_t*vec)

1 返回值:ssize_t

返回值含义上层处理
> 0实际发送的字节数ngx_writev_chain据此推进链指针
NGX_AGAIN(-2)socket 发送缓冲区满,暂不可写调用者设置wev->ready = 0,等待事件通知后重试
NGX_ERROR(-1)发生不可恢复的错误(连接重置等)调用者返回NGX_CHAIN_ERROR,上层关闭连接

注意:本函数不会返回 0——POSIXwritev对 TCP 套接字不会返回 0


2 函数名:ngx_writev

词段含义
ngxNginx 命名空间前缀
writev直接对应 POSIXwritev(2)系统调用,表明这是一个系统调用的封装

3 参数列表

参数名类型含义来源约束
cngx_connection_t *目标 TCP 连接上层ngx_writev_chain的上游调用者传入非 NULL;c->fd为有效 socket fd;c->write指向写事件对象
vecngx_iovec_t *待发送的 iovec 数组封装ngx_writev_chain通过ngx_output_chain_to_iovec填充非 NULL;vec->iovs指向有效的 iovec 数组;vec->count为条目数

2 逻辑流程

ngx_writev(c, vec) │ ├─ [1] 调用 writev 系统调用 │ └─ n = writev(c->fd, vec->iovs, vec->count) │ ├─ [2] 调试日志 │ └─ 记录 "writev: %z of %uz",n 和 vec->size │ ├─ [3] 成功路径 │ └─ n != -1 → 返回 n(实际发送字节数) │ └─ [4] 错误路径(n == -1) ├─ err = ngx_errno(保存 errno) │ ├─ [4.1] EAGAIN 处理 │ └─ err == NGX_EAGAIN → 记录 "not ready",返回 NGX_AGAIN │ ├─ [4.2] EINTR 处理 │ └─ err == NGX_EINTR → 记录 "was interrupted",goto eintr 重试 │ └─ [4.3] 其他错误处理 └─ default → c->write->error = 1,记录日志,返回 NGX_ERROR

3 局部变量声明

{ssize_tn;ngx_err_terr;}

局部变量声明

  • n:保存writev的返回值(已发送字节数或 -1)。
  • err:保存 POSIXerrno的快照(见 [4] 分支的设计意图)。

1 调用 writev 系统调用

eintr:n=writev(c->fd,vec->iovs,vec->count);

进入条件:每次函数被调用时首先执行,以及 EINTR 重试时跳转至此。

处理逻辑
调用 POSIXwritev(2)系统调用,执行 scatter-gather 写操作。参数:

  • c->fd:目标 socket 文件描述符。
    该 fd 在连接建立时被设置为非阻塞模式(O_NONBLOCK),
    因此writev不会阻塞进程。
  • vec->iovs
    由调用者(ngx_output_chain_to_iovec)填充的struct iovec数组,
    每个元素包含iov_base(数据起始地址)和iov_len(数据长度)。
  • vec->count
    iovec 数组中的有效条目数。

writev(2)是 POSIX.1-2001 标准系统调用(声明在<sys/uio.h>),
语义为"将多个不连续内存区域的数据一次性写入文件描述符"。
对于 TCP 套接字,实际发送的字节数可能小于请求的总字节数(vec->size)——
当内核发送缓冲区不足以容纳所有数据时,
writev会发送尽可能多的数据并返回实际发送量。


2 调试日志

ngx_log_debug2(NGX_LOG_DEBUG_EVENT,c->log,0,"writev: %z of %uz",n,vec->size);

处理逻辑
记录一次writev调用的结果:
实际发送字节数n(格式符%z对应ssize_t)和
请求发送的总字节数vec->size(格式符%uz对应size_t)。

设计意图%z of %uz的日志格式直观地展示"实际发送 / 请求发送",
例如"writev: 4096 of 8192"表示只发了一半。这在排查部分发送问题时非常有用。


3 成功路径

returnn;

进入条件n != -1(即writev返回值 >= 0)。

处理逻辑
直接返回n(实际发送的字节数)。
对于 TCP 套接字,n >= 0n <= vec->size
n可能为 0 的极端情况(当所有iov_len均为 0 时),
但 Nginx 的正常路径不会构造空的 iovec。


4 错误路径

if(n==-1){err=ngx_errno;switch(err){caseNGX_EAGAIN:...caseNGX_EINTR:...default:...}}

进入条件writev返回 -1,表示发生错误。

处理逻辑
首先通过ngx_errno保存errno的值。
必须在switch之前立即保存——
因为后续的ngx_log_debug0ngx_connection_error等日志函数内部可能会修改errno
(如调用write到日志文件描述符),
如果在switch中直接使用errno,可能会读到被日志函数覆盖后的错误码。

ngx_errno宏定义src/os/unix/ngx_errno.h):

#definengx_errnoerrno

在 Unix 平台上直接映射为 POSIXerrno


4.1 EAGAIN 处理
caseNGX_EAGAIN:ngx_log_debug0(NGX_LOG_DEBUG_EVENT,c->log,err,"writev() not ready");returnNGX_AGAIN;

进入条件
err == NGX_EAGAIN(即errno == EAGAINerrno == EWOULDBLOCK)。

处理逻辑
EAGAIN表示 socket 发送缓冲区已满,数据暂时无法写入。
这是非阻塞 I/O 的正常信号,不是真正的错误。
记录一条调试日志后,返回NGX_AGAIN(-2)。
调用者ngx_writev_chain会将wev->ready置为 0,
然后等待 epoll/kqueue 通知 socket 再次可写。


4.2 EINTR 处理
caseNGX_EINTR:ngx_log_debug0(NGX_LOG_DEBUG_EVENT,c->log,err,"writev() was interrupted");gotoeintr;

进入条件
err == NGX_EINTR(即writev被信号处理程序中断)。

处理逻辑
EINTR是 POSIX 信号安全语义的一部分:
当进程收到信号并执行了信号处理函数后,
某些"慢系统调用"(slow system call)会返回 -1 并将errno设为EINTR
对于writev,此时没有数据被发送,调用者应当重新发起系统调用。
代码通过goto eintr跳回writev调用点,实现自动重试。


4.3 其他错误处理
default:c->write->error=1;ngx_connection_error(c,err,"writev() failed");returnNGX_ERROR;

进入条件
err不是EAGAIN也不是EINTR——即发生了真正的错误。

处理逻辑
分两步:

  1. 设置错误标志
    c->write->error = 1c->write是连接的写事件对象(ngx_event_t),
    error位字段标记该事件已进入错误状态。
    上层事件处理循环会检测此标志,触发连接关闭流程。

  2. 记录错误日志
    调用ngx_connection_error(c, err, "writev() failed")
    根据错误类型以适当的日志级别记录错误信息。

  3. 返回NGX_ERROR(-1),通知调用者发生了不可恢复的错误。

设计意图

  • c->write->error = 1在日志记录之前设置,
    这是 Nginx 的错误处理惯例——先标记状态,再记录日志。
    如果日志记录本身触发了错误(极端情况),状态已经正确反映。

  • 返回NGX_ERROR而非直接关闭连接,将关闭决策留给上层——保持职责分离。


http://www.jsqmd.com/news/1238570/

相关文章:

  • 你的 AI 不是下属,是组织:为什么多智能体要借管理学和社会学来建
  • HarmonyOS 6.1 AI融合实战:端侧智能与HiAI Foundation的极致性能
  • C++11 std::function与std::bind核心用法与实现原理
  • SpringBlade Sword:企业级微服务前端开发终极指南
  • HarmonyOS 6.1 生态整合实战:服务卡片与“万能卡片”的生态玩法
  • TI HDVPSS数据通路配置:从寄存器解析到画中画实战
  • 嵌入式EMIFA接口与NAND Flash时序配置实战:从理论计算到驱动调试
  • JSON与JSONPATH:数据查询与处理核心技术解析
  • Agent 大厂面试题・|字节跳动|淘汰85%候选人的AI综合面,Agent全链路连环追问附满分作答
  • 有故事但不会画画?这5款AI工具帮你一键生成漫画
  • LeetCode hot 100—1
  • 移动性水文监测物联网解决方案
  • Dockerfile核心指令详解与容器化最佳实践
  • 解密Palantir系列三:6.AIP · 别再把 AIP 当成一个聊天框:四个入口,四种工作
  • PCB贴片打样厂家如何选择?速度、品质与交付能力缺一不可
  • 西安劳力士回收价格查询及各大平台实测**2026年7月最新数据) - 天价名表回收平台
  • 架构思维核心原则与设计模式实战解析
  • PCB原理图设计规范与信号完整性要点解析
  • Flutter 是否会淘汰 iOS 原生工程师?深度解析跨平台与原生开发的未来
  • 使用Bochs调试Linux 0.11内核的实践指南
  • EMAC统计寄存器:嵌入式网络调试与性能监控实战指南
  • 构建高效个人知识管理系统:从碎片到体系
  • jemalloc与TLB shootdown性能问题分析与优化
  • 证件照处理API技术解析与应用实践
  • BioClaw生物信息学自动化分析工具全解析
  • Unity大型项目开发避坑指南:架构、性能与资源管理实战
  • 如何在5分钟内用OBS插件实现专业级AI背景移除?终极完整指南
  • 苏州爱彼回收价格查询及各大回收平台实测**2026年7月最新数据) - 尊奢回收二奢平台
  • Hive表操作全解析与大数据处理优化实践
  • RAG知识库问答系统落地:从向量检索到上下文增强的全链路实践