1 定义ngx_writev 函数 定义在 src/os/unix/ngx_writev_chain.cssize_tngx_writev(ngx_connection_t*c,ngx_iovec_t*vec){ssize_tn;ngx_err_terr;eintr:nwritev(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){errngx_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-error1;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-iovs、vec-count。错误码归一化将 POSIX 的errnoEAGAIN、EINTR等映射为 Nginx 内部统一的错误码NGX_AGAIN、NGX_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-2socket 发送缓冲区满暂不可写调用者设置wev-ready 0等待事件通知后重试NGX_ERROR-1发生不可恢复的错误连接重置等调用者返回NGX_CHAIN_ERROR上层关闭连接注意本函数不会返回 0——POSIXwritev对 TCP 套接字不会返回 02 函数名ngx_writev词段含义ngxNginx 命名空间前缀writev直接对应 POSIXwritev(2)系统调用表明这是一个系统调用的封装3 参数列表参数名类型含义来源约束cngx_connection_t *目标 TCP 连接上层ngx_writev_chain的上游调用者传入非 NULLc-fd为有效 socket fdc-write指向写事件对象vecngx_iovec_t *待发送的 iovec 数组封装ngx_writev_chain通过ngx_output_chain_to_iovec填充非 NULLvec-iovs指向有效的 iovec 数组vec-count为条目数2 逻辑流程ngx_writev(c, vec) │ ├─ [1] 调用 writev 系统调用 │ └─ n writev(c-fd, vec-iovs, vec-count) │ ├─ [2] 调试日志 │ └─ 记录 writev: %z of %uzn 和 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 interruptedgoto eintr 重试 │ └─ [4.3] 其他错误处理 └─ default → c-write-error 1记录日志返回 NGX_ERROR3 局部变量声明{ssize_tn;ngx_err_terr;}局部变量声明n保存writev的返回值已发送字节数或 -1。err保存 POSIXerrno的快照见 [4] 分支的设计意图。1 调用 writev 系统调用eintr:nwritev(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-countiovec 数组中的有效条目数。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 0且n vec-size。n可能为 0 的极端情况当所有iov_len均为 0 时但 Nginx 的正常路径不会构造空的 iovec。4 错误路径if(n-1){errngx_errno;switch(err){caseNGX_EAGAIN:...caseNGX_EINTR:...default:...}}进入条件writev返回 -1表示发生错误。处理逻辑首先通过ngx_errno保存errno的值。必须在switch之前立即保存——因为后续的ngx_log_debug0、ngx_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 EAGAIN或errno 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-error1;ngx_connection_error(c,err,writev() failed);returnNGX_ERROR;进入条件err不是EAGAIN也不是EINTR——即发生了真正的错误。处理逻辑分两步设置错误标志c-write-error 1。c-write是连接的写事件对象ngx_event_t其error位字段标记该事件已进入错误状态。上层事件处理循环会检测此标志触发连接关闭流程。记录错误日志调用ngx_connection_error(c, err, writev() failed)根据错误类型以适当的日志级别记录错误信息。返回NGX_ERROR-1通知调用者发生了不可恢复的错误。设计意图c-write-error 1在日志记录之前设置这是 Nginx 的错误处理惯例——先标记状态再记录日志。如果日志记录本身触发了错误极端情况状态已经正确反映。返回NGX_ERROR而非直接关闭连接将关闭决策留给上层——保持职责分离。