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

相关文章

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型的操作流程

《0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeekR1模型的操作流程》DeepSeekR1模型凭借其强大的自然语言处理能力,在未来具有广阔的应用前景,有望在多个领域发... 目录0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型,3步搞定一个应

nginx部署https网站的实现步骤(亲测)

《nginx部署https网站的实现步骤(亲测)》本文详细介绍了使用Nginx在保持与http服务兼容的情况下部署HTTPS,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值... 目录步骤 1:安装 Nginx步骤 2:获取 SSL 证书步骤 3:手动配置 Nginx步骤 4:测

Windows设置nginx启动端口的方法

《Windows设置nginx启动端口的方法》在服务器配置与开发过程中,nginx作为一款高效的HTTP和反向代理服务器,被广泛应用,而在Windows系统中,合理设置nginx的启动端口,是确保其正... 目录一、为什么要设置 nginx 启动端口二、设置步骤三、常见问题及解决一、为什么要设置 nginx

Python利用自带模块实现屏幕像素高效操作

《Python利用自带模块实现屏幕像素高效操作》这篇文章主要为大家详细介绍了Python如何利用自带模块实现屏幕像素高效操作,文中的示例代码讲解详,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、获取屏幕放缩比例2、获取屏幕指定坐标处像素颜色3、一个简单的使用案例4、总结1、获取屏幕放缩比例from

nginx-rtmp-module模块实现视频点播的示例代码

《nginx-rtmp-module模块实现视频点播的示例代码》本文主要介绍了nginx-rtmp-module模块实现视频点播,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习... 目录预置条件Nginx点播基本配置点播远程文件指定多个播放位置参考预置条件配置点播服务器 192.

nginx-rtmp-module构建流媒体直播服务器实战指南

《nginx-rtmp-module构建流媒体直播服务器实战指南》本文主要介绍了nginx-rtmp-module构建流媒体直播服务器实战指南,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有... 目录1. RTMP协议介绍与应用RTMP协议的原理RTMP协议的应用RTMP与现代流媒体技术的关系2

解决systemctl reload nginx重启Nginx服务报错:Job for nginx.service invalid问题

《解决systemctlreloadnginx重启Nginx服务报错:Jobfornginx.serviceinvalid问题》文章描述了通过`systemctlstatusnginx.se... 目录systemctl reload nginx重启Nginx服务报错:Job for nginx.javas

多模块的springboot项目发布指定模块的脚本方式

《多模块的springboot项目发布指定模块的脚本方式》该文章主要介绍了如何在多模块的SpringBoot项目中发布指定模块的脚本,作者原先的脚本会清理并编译所有模块,导致发布时间过长,通过简化脚本... 目录多模块的springboot项目发布指定模块的脚本1、不计成本地全部发布2、指定模块发布总结多模

MySQL中my.ini文件的基础配置和优化配置方式

《MySQL中my.ini文件的基础配置和优化配置方式》文章讨论了数据库异步同步的优化思路,包括三个主要方面:幂等性、时序和延迟,作者还分享了MySQL配置文件的优化经验,并鼓励读者提供支持... 目录mysql my.ini文件的配置和优化配置优化思路MySQL配置文件优化总结MySQL my.ini文件

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

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