news 2026/5/10 17:03:36

ngx_unix_recv

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ngx_unix_recv

1 定义

ngx_unix_recv 函数 定义在 ./nginx-1.24.0/src/os/unix/ngx_recv.c
ssize_tngx_unix_recv(ngx_connection_t*c,u_char*buf,size_tsize){ssize_tn;ngx_err_terr;ngx_event_t*rev;rev=c->read;#if(NGX_HAVE_KQUEUE)if(ngx_event_flags&NGX_USE_KQUEUE_EVENT){ngx_log_debug3(NGX_LOG_DEBUG_EVENT,c->log,0,"recv: eof:%d, avail:%d, err:%d",rev->pending_eof,rev->available,rev->kq_errno);if(rev->available==0){if(rev->pending_eof){rev->ready=0;rev->eof=1;if(rev->kq_errno){rev->error=1;ngx_set_socket_errno(rev->kq_errno);returnngx_connection_error(c,rev->kq_errno,"kevent() reported about an closed connection");}return0;}else{rev->ready=0;returnNGX_AGAIN;}}}#endif#if(NGX_HAVE_EPOLLRDHUP)if((ngx_event_flags&NGX_USE_EPOLL_EVENT)&&ngx_use_epoll_rdhup){ngx_log_debug2(NGX_LOG_DEBUG_EVENT,c->log,0,"recv: eof:%d, avail:%d",rev->pending_eof,rev->available);if(rev->available==0&&!rev->pending_eof){rev->ready=0;returnNGX_AGAIN;}}#endifdo{n=recv(c->fd,buf,size,0);ngx_log_debug3(NGX_LOG_DEBUG_EVENT,c->log,0,"recv: fd:%d %z of %uz",c->fd,n,size);if(n==0){rev->ready=0;rev->eof=1;#if(NGX_HAVE_KQUEUE)/* * on FreeBSD recv() may return 0 on closed socket * even if kqueue reported about available data */if(ngx_event_flags&NGX_USE_KQUEUE_EVENT){rev->available=0;}#endifreturn0;}if(n>0){#if(NGX_HAVE_KQUEUE)if(ngx_event_flags&NGX_USE_KQUEUE_EVENT){rev->available-=n;/* * rev->available may be negative here because some additional * bytes may be received between kevent() and recv() */if(rev->available<=0){if(!rev->pending_eof){rev->ready=0;}rev->available=0;}returnn;}#endif#if(NGX_HAVE_FIONREAD)if(rev->available>=0){rev->available-=n;/* * negative rev->available means some additional bytes * were received between kernel notification and recv(), * and therefore ev->ready can be safely reset even for * edge-triggered event methods */if(rev->available<0){rev->available=0;rev->ready=0;}ngx_log_debug1(NGX_LOG_DEBUG_EVENT,c->log,0,"recv: avail:%d",rev->available);}elseif((size_t)n==size){if(ngx_socket_nread(c->fd,&rev->available)==-1){n=ngx_connection_error(c,ngx_socket_errno,ngx_socket_nread_n" failed");break;}ngx_log_debug1(NGX_LOG_DEBUG_EVENT,c->log,0,"recv: avail:%d",rev->available);}#endif#if(NGX_HAVE_EPOLLRDHUP)if((ngx_event_flags&NGX_USE_EPOLL_EVENT)&&ngx_use_epoll_rdhup){if((size_t)n<size){if(!rev->pending_eof){rev->ready=0;}rev->available=0;}returnn;}#endifif((size_t)n<size&&!(ngx_event_flags&NGX_USE_GREEDY_EVENT)){rev->ready=0;}returnn;}err=ngx_socket_errno;if(err==NGX_EAGAIN||err==NGX_EINTR){ngx_log_debug0(NGX_LOG_DEBUG_EVENT,c->log,err,"recv() not ready");n=NGX_AGAIN;}else{n=ngx_connection_error(c,err,"recv() failed");break;}}while(err==NGX_EINTR);rev->ready=0;if(n==NGX_ERROR){rev->error=1;}returnn;}
ngx_unix_recv 函数 是 Nginx 在 Unix/Linux 系统上从套接字接收数据的核心底层函数。 它封装了 `recv()` 系统调用, 并根据当前使用的 I/O 多路复用机制及触发模式(边缘/水平), 动态管理读事件的 `ready`、`available`(可读字节数)、`eof` 和 `error` 等状态。 同时,该函数处理信号中断(EINTR)重试、无数据可读(EAGAIN) 返回 `NGX_AGAIN`、正常关闭返回 0, 以及异常错误返回 `NGX_ERROR`, 为上层事件驱动模型提供一致且高效的数据接收接口。

2 详解

1 函数签名

ssize_tngx_unix_recv(ngx_connection_t*c,u_char*buf,size_tsize)
返回值 > 0:成功读取的字节数,可能小于等于 size。 0:对端已关闭连接(EOF),此时函数会将读事件的 eof 标志置为 1。 负值:表示特殊情况或错误,具体由 Nginx 定义的宏控制: NGX_AGAIN(通常为 -2): 表示非阻塞套接字当前无数据可读,或事件尚未就绪,调用者应将读事件重新加入事件循环等待通知。 NGX_ERROR(通常为 -1): 表示发生不可恢复的错误,函数已处理连接错误并设置事件错误标志。
参数1 ngx_connection_t *c 指向一个连接对象 c->fd:套接字文件描述符,是 recv() 操作的目标 参数2 u_char *buf 接收数据的存储区,由调用者预先分配,大小至少为 size 字节 参数3 size_t size 指定了接收缓冲区的容量,即本次调用最多能读取的字节数

2 逻辑流程

1 局部变量 2 Kqueue 处理 3 无需调用 recv 4 接收循环 5 循环后统一状态更新与返回

1 局部变量
{ssize_tn;ngx_err_terr;ngx_event_t*rev;rev=c->read;
初始化 rev:获取连接 c 的读事件对象

2 Kqueue 处理
#if(NGX_HAVE_KQUEUE)if(ngx_event_flags&NGX_USE_KQUEUE_EVENT){ngx_log_debug3(NGX_LOG_DEBUG_EVENT,c->log,0,"recv: eof:%d, avail:%d, err:%d",rev->pending_eof,rev->available,rev->kq_errno);if(rev->available==0){if(rev->pending_eof){rev->ready=0;rev->eof=1;if(rev->kq_errno){rev->error=1;ngx_set_socket_errno(rev->kq_errno);returnngx_connection_error(c,rev->kq_errno,"kevent() reported about an closed connection");}return0;}else{rev->ready=0;returnNGX_AGAIN;}}}#endif

3 无需调用 recv
#if(NGX_HAVE_EPOLLRDHUP)if((ngx_event_flags&NGX_USE_EPOLL_EVENT)&&ngx_use_epoll_rdhup){ngx_log_debug2(NGX_LOG_DEBUG_EVENT,c->log,0,"recv: eof:%d, avail:%d",rev->pending_eof,rev->available);if(rev->available==0&&!rev->pending_eof){rev->ready=0;returnNGX_AGAIN;}}#endif
#1 当前事件模块是 epoll 配置启用了 epoll_rdhup 允许使用 EPOLLRDHUP 检测对端半关闭。 #2 调试日志 #3 判断是否需要真正调用 recv(): 若 epoll 指示没有数据可读(available == 0) 且并未因对端关闭而触发(!pending_eof), 则直接避免无意义的 recv() 调用。 返回 NGX_AGAIN:清除 ready 标志,重新等待事件。 这是边缘触发模式下的重要优化,可以减少系统调用开销。

4 接收循环
do{n=recv(c->fd,buf,size,0);ngx_log_debug3(NGX_LOG_DEBUG_EVENT,c->log,0,"recv: fd:%d %z of %uz",c->fd,n,size);if(n==0){rev->ready=0;rev->eof=1;#if(NGX_HAVE_KQUEUE)/* * on FreeBSD recv() may return 0 on closed socket * even if kqueue reported about available data */if(ngx_event_flags&NGX_USE_KQUEUE_EVENT){rev->available=0;}#endifreturn0;}if(n>0){#if(NGX_HAVE_KQUEUE)if(ngx_event_flags&NGX_USE_KQUEUE_EVENT){rev->available-=n;/* * rev->available may be negative here because some additional * bytes may be received between kevent() and recv() */if(rev->available<=0){if(!rev->pending_eof){rev->ready=0;}rev->available=0;}returnn;}#endif#if(NGX_HAVE_FIONREAD)if(rev->available>=0){rev->available-=n;/* * negative rev->available means some additional bytes * were received between kernel notification and recv(), * and therefore ev->ready can be safely reset even for * edge-triggered event methods */if(rev->available<0){rev->available=0;rev->ready=0;}ngx_log_debug1(NGX_LOG_DEBUG_EVENT,c->log,0,"recv: avail:%d",rev->available);}elseif((size_t)n==size){if(ngx_socket_nread(c->fd,&rev->available)==-1){n=ngx_connection_error(c,ngx_socket_errno,ngx_socket_nread_n" failed");break;}ngx_log_debug1(NGX_LOG_DEBUG_EVENT,c->log,0,"recv: avail:%d",rev->available);}#endif#if(NGX_HAVE_EPOLLRDHUP)if((ngx_event_flags&NGX_USE_EPOLL_EVENT)&&ngx_use_epoll_rdhup){if((size_t)n<size){if(!rev->pending_eof){rev->ready=0;}rev->available=0;}returnn;}#endifif((size_t)n<size&&!(ngx_event_flags&NGX_USE_GREEDY_EVENT)){rev->ready=0;}returnn;}err=ngx_socket_errno;if(err==NGX_EAGAIN||err==NGX_EINTR){ngx_log_debug0(NGX_LOG_DEBUG_EVENT,c->log,err,"recv() not ready");n=NGX_AGAIN;}else{n=ngx_connection_error(c,err,"recv() failed");break;}}while(err==NGX_EINTR);
#1 系统调用 recv: 从连接套接字 c->fd 读取数据到 buf, 最多 size 字节,flags 为 0(无特殊标志)。 返回值存入 n。
#2-1 情况一:n == 0 —— 对端关闭连接 检查返回值是否 0: recv() 返回 0 表示对端已执行了关闭写入(发送了 FIN),TCP 连接半关闭或完全关闭。 更新事件状态:清除 ready 置 eof = 1,保证上层逻辑能正确处理 EOF。
#2-2 Kqueue 特化清理:kqueue 模型下, 可能还存在残留的 available 值,这里强行归零,避免状态错乱。
#2-3 返回 0:通知上层连接已关闭。
#3-1 情况二:n > 0 —— 成功读取数据
#3-2 Kqueue 机制下的 available 更新
#3-3 条件编译:支持 FIONREAD ioctl(如 Linux、FreeBSD 等), 可以查询套接字接收缓冲区中待读字节数。 如果已知 available: 进入基于该值的递减逻辑。 递减可用字节数 处理负值情况: 若 available 变成负数,说明有额外数据到达, 因此清 available 并置 ready = 0(边缘触发下表示本次通知数据已被取完)。 记录剩余量:便于调试。
available 未知(初始为 -1)且本次读满了: 说明缓冲区可能还有更多数据,但之前没有获取 available, 现在需要主动探测。 通过 ioctl 查询: 调用 ngx_socket_nread 获取套接字接收缓冲区未读字节数存入 rev->available。 如果 ioctl 失败,调用 ngx_connection_error 记录错误,设置 n = NGX_ERROR, 然后 break 跳出循环,最终由尾部统一处理返回。
#3-4 EPOLLRDHUP 模式下的就绪状态管理 epoll + EPOLLRDHUP 环境下 判断本次读取是否未填满缓冲区: 若 n < size,通常表示内核缓冲区已被读空(已完成本次事件通知的所有数据)。 更新状态: 若没有挂起的 EOF,则清除 ready(边缘触发下已无数据可读,不再就绪)。 将 available 强制置 0,表示无剩余数据。 返回实际读取字节数, 跳过后续通用的 ready 调整(因为 epoll 路径已完成专门处理)。
#3-5 基于 GREEDY_EVENT 的就绪处理 (size_t) n < size: 读取量小于请求量,暗示内核缓冲区已空。 !(ngx_event_flags & NGX_USE_GREEDY_EVENT): 当前事件模块不是“贪心”模式。 在 Nginx 中,水平触发(如 select)通常也设为非贪心, 表示单次事件通知只读取一次(而不在循环中直到 EAGAIN), 因此这里清除 ready,让事件循环下次再次通知。 返回实际读取字节数
#4 情况三:n < 0 —— recv() 返回错误 获取错误码
#4-1 判断是否为可恢复错误: NGX_EAGAIN:非阻塞套接字无数据可读(等同于 EWOULDBLOCK)。 NGX_EINTR:系统调用被信号中断。 设置返回值为 NGX_AGAIN:告诉上层需要再次等待事件 while (err == NGX_EINTR) 会判断: 如果是 EINTR,err == NGX_EINTR 成立,循环会回到 do 开头重新执行 recv()。 如果是 EAGAIN,err == NGX_EINTR 不成立,循环退出,保留 n = NGX_AGAIN 作为最终返回值。
#4-2 不可恢复的其他错误 ngx_connection_error 会记录错误日志并返回 NGX_ERROR。 n 被赋值为 NGX_ERROR(内部通常为 -1)。 break 退出 do-while 循环。

循环条件:仅当错误是 EINTR(信号中断)时继续循环,重新调用 recv()。 对于 EAGAIN 或已通过 break 退出的错误,循环终止。

5 循环后统一状态更新与返回
rev->ready=0;if(n==NGX_ERROR){rev->error=1;}returnn;}
#! 循环后统一状态更新与返回 清除 ready 标志:无论因 EAGAIN 退出还是出现错误, 此时套接字已无数据可读,必须置零,防止事件循环误认为仍然就绪。
#2 标记错误状态: 若最终返回值为 NGX_ERROR(不可恢复错误), 在读事件上设置 error = 1,以便上层销毁连接或进行清理。
#3 返回最终结果: n > 0:成功读取的字节数。 0:正常 EOF(在前面的 if (n == 0) 分支已提前返回,不会执行到这里,但逻辑完备)。 NGX_AGAIN:暂时无数据,需重新等待。 NGX_ERROR:连接发生致命错误。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/10 16:59:55

Taotoken用量看板如何帮助团队清晰掌控API成本

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 Taotoken用量看板如何帮助团队清晰掌控API成本 对于使用大模型API进行开发的团队而言&#xff0c;成本控制一直是一个核心的管理挑…

作者头像 李华
网站建设 2026/5/10 16:59:46

构建ai客服系统时利用taotoken多模型能力实现降级与择优策略

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 构建AI客服系统时利用Taotoken多模型能力实现降级与择优策略 在构建面向真实用户的AI客服或对话系统时&#xff0c;服务的稳定性和…

作者头像 李华
网站建设 2026/5/10 16:45:08

3个关键场景重塑Web邮件体验:Roundcube Mail深度实践指南

3个关键场景重塑Web邮件体验&#xff1a;Roundcube Mail深度实践指南 【免费下载链接】roundcubemail The Roundcube Webmail suite 项目地址: https://gitcode.com/gh_mirrors/ro/roundcubemail 你是否曾为传统邮件客户端的笨重界面而烦恼&#xff1f;或是为移动设备上…

作者头像 李华
网站建设 2026/5/10 16:42:32

新手入门教程五分钟完成Python项目对接Taotoken大模型API

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 新手入门教程五分钟完成Python项目对接Taotoken大模型API 对于刚接触AI接口的Python开发者而言&#xff0c;将大模型能力集成到自己…

作者头像 李华