Nginx基础. epoll事件驱动模块

2024-06-22 19:48

本文主要是介绍Nginx基础. epoll事件驱动模块,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

关于epoll事件驱动模块, 这里不做过多分析. 主要着眼于事件添加和事件处理上.

添加事件

static ngx_int_t
ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
{int                  op;uint32_t             events, prev;ngx_event_t         *e;ngx_connection_t    *c;struct epoll_event   ee;//之前有过了解, 每个事件的data可能存储的是连接结构体//每个连接都是一个"事件", 且每个连接对应于读写池的两个事件c = ev->data;//这里的event用于确定当前事件是读事件还是写事件. 即将来调用epoll_ctl是使用EPOLLIN还是EPOLLOUTevents = (uint32_t) event;//如果当前事件是以读事件插入, 那么事件对象e就会记录下此连接事件的写事件(当然, 写事件可能并不存在, 但即使写//事件不存在, 它的事件对象早已被创建好, 其中的active标志就反映了该事件是否被定义). //与此同时, 记住此时prev值是EPOLLOUT, events则为读标记if (event == NGX_READ_EVENT) {e = c->write;prev = EPOLLOUT;
#if (NGX_READ_EVENT != EPOLLIN|EPOLLRDHUP)events = EPOLLIN|EPOLLRDHUP;
#endif} else {e = c->read;prev = EPOLLIN|EPOLLRDHUP;
#if (NGX_WRITE_EVENT != EPOLLOUT)events = EPOLLOUT;
#endif} //上面, 如果某事件作为读事件插入, 那么e记录的是该连接写事件. //也说道, active记录了该事件是否激活(被定义).//如果我们在插入读事件的时候, 发现该连接的写事件曾经已经被插入到epoll中了, 也就是说该连接的描述符已经被插入了//那么, 现在我们可能就会重复插入. 为了避免这样的问题, 就有了下面的判断//如果该描述符已经被注册过了, 那么不是添加描述符而是修改描述符//且事件类型修改为读写if (e->active) {op = EPOLL_CTL_MOD;events |= prev;} else {//否则就是添加描述符op = EPOLL_CTL_ADD;}ee.events = events | (uint32_t) flags;//插入到epoll中的描述符对应的存储信息的结构体的data.ptr用于存储连接结构体//这里可能是个令人疑惑的地方, 但也是特别需要在意的地方.//这个instance标志是判断一个事件是否过期的标志. 下面会涉及到.ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,"epoll add event: fd:%d op:%d ev:%08XD",c->fd, op, ee.events);if (epoll_ctl(ep, op, c->fd, &ee) == -1) {...return NGX_OK;
}


收集分发事件

接下来就是分析实现了 收集分发事件的process_event方法.
每个worker子进程主要就是调用了这里的方法进行事件的收集分发
static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{int                events;uint32_t           revents;ngx_int_t          instance, i;ngx_uint_t         level;ngx_err_t          err;ngx_event_t       *rev, *wev;ngx_queue_t       *queue;ngx_connection_t  *c;/* NGX_TIMER_INFINITE == INFTIM */ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,"epoll timer: %M", timer);events = epoll_wait(ep, event_list, (int) nevents, timer);err = (events == -1) ? ngx_errno : 0;//还记得这里的ngx_event_timer_alarm吗, 就是利用setitimer开启的间歇定时器if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {ngx_time_update();}//如果发生了错误if (err) {//被信号打断. 有可能是被定时器信号打断的if (err == NGX_EINTR) {//每次定时器信号发生后的信号处理函数都会将这个变量置1.if (ngx_event_timer_alarm) {//表明的确是被定时器打断的//上面已经更新过时间了, 所以重置为0.ngx_event_timer_alarm = 0;return NGX_OK;}. . .}//因为epoll返回了0, 要么是因为epoll_wait的时间参数超时了, 要么就是出错了if (events == 0) {if (timer != NGX_TIMER_INFINITE) {return NGX_OK;}. . .}//处理每一个发生的事件for (i = 0; i < events; i++) {//得到该事件的连接(从连接中我们可以得到其对应的读写事件(如果存在的话))c = event_list[i].data.ptr;//取出存在地址最后一位的instance变量instance = (uintptr_t) c & 1;//还原连接结构体的原本地址c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);//得到连接对应的读写事件的instance成员的值rev = c->read;//如果发现fd变成-1那么说明这个连接已经过期了//如果发现此连接的fd并没有变成-1, 这说明此事件对应的连接可能已经过期了, 但此连接使用的结构体虽然被free但又被其他新连接使用了//这时候需要看instance是否发生变化. 如果不再与原来相同, 那么表明确实是被其他新连接复用了; 相同则表明没有过期//过期的事件就不再需要处理if (c->fd == -1 || rev->instance != instance) {/** the stale event from a file descriptor* that was just closed in this iteration*/ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,"epoll: stale event %p", c);continue;}//得到此事件的事件类型revents = event_list[i].events;...if ((revents & EPOLLIN) && rev->active) {...//表示需要延后处理if (flags & NGX_POST_EVENTS) {//对于EPOLLIN事件, 其有可能是普通的读事件, 也有可能是有新连接到来 //之所以要将他们区别对待, 之后会讲到queue = rev->accept ? &ngx_posted_accept_events: &ngx_posted_events;ngx_post_event(rev, queue);} else {//否则直接调用回调函数处理rev->handler(rev);}}wev = c->write;if ((revents & EPOLLOUT) && wev->active) {if (c->fd == -1 || wev->instance != instance) {/** the stale event from a file descriptor* that was just closed in this iteration*/ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,"epoll: stale event %p", c);continue;}wev->ready = 1;if (flags & NGX_POST_EVENTS) {ngx_post_event(wev, &ngx_posted_events);} else {wev->handler(wev);}}}return NGX_OK;
}



ngx_event_process_events方法会收集当前触发的所有事件. 对于不需要加入到post队列延后处理的事件, 该方法会立即执行他们的回调方法. 这其实是在做分发事件的工作, 只是它会在自己的进程中调用这些方法而已, 因此每个回调方法都不能导致进程休眠或者消耗太多时间, 以免epoll不能及时处理其他事件.


关于ngx_event_t中的instance标志位

它为什么能判断事件是否过期呢?

1.instance放在哪里 ?
它利用了指针最后一位一定是0的特性. 既然最后一位始终是0, 那么就可以用来包含一些信息.
这样, 在使用ngx_epoll_add_event方法向epoll中添加事件时, 就把epoll_event中data成员的ptr指针指向连接结构体的同时,把最后一位置为这个事件的instance标志.
在ngx_event_process_events方法中取出指向连接结构体的指针时, 先把instance取出, 再把最后一位还原成原本连接结构体的地址.
2. 为什么一定要放在这一位?
因为ptr指针已经指向了连接结构体, 然而连接结构体中并没有这个成员变量. 但这个变量又是必需的, 所以得找个地方存它
3. 什么是过期事件?
举个例子, 假设epoll_wait一次返回了3个事件. 在第一个事件处理的过程中,  由于业务的需要, 所以关闭了一个连接. 而这个连接恰好是第三个事件的连接, 这样一来, 在处理到第三个事件的时候, 这个事件已经是过期事件了, 一旦处理就肯定会出错了. 既然如此, 解决的方案可能并不需要instance, 如果把该连接的fd设置为-1是否就能解决这个问题呢?
假设第三个事件对应的连接结构体中的fd套接字原先是50, 处理第一个事件时把这个连接套接字关闭了, 并将fd设置为-1, 并调用ngx_free_connection将该连接归还给连接池. 当我们处理第二个连接的时候, 恰好第二个事件是建立新的连接(属于监听套接字的事件), 调用ngx_get_connection从连接池取出的连接结构体恰好就是刚刚归还给连接池的第三个事件的连接.  又由于套接字50刚刚被释放了, linux内核很可能把这个刚释放的套接字50又分配给新建立的连接. 因此, 在处理到第三个事件的时候, 这个连接应该是过期的而不能被处理!
如何解决这个问题就是依靠instance了.
当调用ngx_get_connection从连接池获取一个新连接时, instance位就会被置反:
ngx_connection_t *
ngx_get_connection(ngx_socket_t s, ngx_log_t *log)
{...c = ngx_cycle->free_connections;...ngx_cycle->free_connections = c->data;ngx_cycle->free_connection_n--;...//可以看到, 新获得的连接却依然持有以前的连接对应的读写事件rev = c->read;wev = c->write;ngx_memzero(c, sizeof(ngx_connection_t));//并在初始化连接结构体后依然把原本的事件放回去.c->read = rev;c->write = wev;c->fd = s;c->log = log;instance = rev->instance;//初始化读写事件ngx_memzero(rev, sizeof(ngx_event_t));ngx_memzero(wev, sizeof(ngx_event_t));//将读写事件的instance置反rev->instance = !instance;wev->instance = !instance;...return c;
}
这样处理后, 当某个连接结构体被重复使用后, 它的instance位一定是不同的. 因此, 在ngx_epoll_process_events方法中一旦判断instance发生了变化, 就认为这是过期事件而不处理.

这篇关于Nginx基础. epoll事件驱动模块的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1085248

相关文章

centos7基于keepalived+nginx部署k8s1.26.0高可用集群

《centos7基于keepalived+nginx部署k8s1.26.0高可用集群》Kubernetes是一个开源的容器编排平台,用于自动化地部署、扩展和管理容器化应用程序,在生产环境中,为了确保集... 目录一、初始化(所有节点都执行)二、安装containerd(所有节点都执行)三、安装docker-

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

使用Nginx来共享文件的详细教程

《使用Nginx来共享文件的详细教程》有时我们想共享电脑上的某些文件,一个比较方便的做法是,开一个HTTP服务,指向文件所在的目录,这次我们用nginx来实现这个需求,本文将通过代码示例一步步教你使用... 在本教程中,我们将向您展示如何使用开源 Web 服务器 Nginx 设置文件共享服务器步骤 0 —

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

一文带你搞懂Nginx中的配置文件

《一文带你搞懂Nginx中的配置文件》Nginx(发音为“engine-x”)是一款高性能的Web服务器、反向代理服务器和负载均衡器,广泛应用于全球各类网站和应用中,下面就跟随小编一起来了解下如何... 目录摘要一、Nginx 配置文件结构概述二、全局配置(Global Configuration)1. w

python中的与时间相关的模块应用场景分析

《python中的与时间相关的模块应用场景分析》本文介绍了Python中与时间相关的几个重要模块:`time`、`datetime`、`calendar`、`timeit`、`pytz`和`dateu... 目录1. time 模块2. datetime 模块3. calendar 模块4. timeit

Python模块导入的几种方法实现

《Python模块导入的几种方法实现》本文主要介绍了Python模块导入的几种方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录一、什么是模块?二、模块导入的基本方法1. 使用import整个模块2.使用from ... i

若依部署Nginx和Tomcat全过程

《若依部署Nginx和Tomcat全过程》文章总结了两种部署方法:Nginx部署和Tomcat部署,Nginx部署包括打包、将dist文件拉到指定目录、配置nginx.conf等步骤,Tomcat部署... 目录Nginx部署后端部署Tomcat部署出现问题:点击刷新404总结Nginx部署第一步:打包

Nginx、Tomcat等项目部署问题以及解决流程

《Nginx、Tomcat等项目部署问题以及解决流程》本文总结了项目部署中常见的four类问题及其解决方法:Nginx未按预期显示结果、端口未开启、日志分析的重要性以及开发环境与生产环境运行结果不一致... 目录前言1. Nginx部署后未按预期显示结果1.1 查看Nginx的启动情况1.2 解决启动失败的

tomcat在nginx中的配置方式

《tomcat在nginx中的配置方式》文章介绍了如何在Linux系统上安装和配置Tomcat,并通过Nginx进行代理,首先,下载并解压Tomcat压缩包,然后启动Tomcat并查看日志,接着,配置... 目录一、下载安装tomcat二、启动tomcat三、配置nginx总结提示:文章写完后,目录可以自动