文章5:Nginx源码分析--事件循环
来源:互联网 发布:linux 图形界面 编辑:程序博客网 时间:2024/06/06 13:21
欢迎转载,转载请注明出处http://blog.csdn.net/yankai0219/article/details/8453297
文章内容
0.序
1.概述:
2.几个变量的作用
3.ngx_process_events_and_timers结构图
4.ngx_process_events_and_timers函数详解
5.分析Nginx对accept事件的处理
6.小结
0.序:
本文学习了阿里巴巴大牛们Nginx源码分析-事件循环的文章,并对于里面内容进行了更为详尽的分析。并且参考了众多网上文章http://bollaxu.iteye.com/blog/855457
1.概述:
事件循环这个概念貌似在windows编程中提得更多,Linux程序却很少提及这个概念。本文所提及的事件循环其实就是worker cycle,由于此处将关注的不再是worker进程,而是worker进程在循环过程中关于事件处理的环节,因此就盗用了事件循环这个概念。在具体看代码前,先看一下这个“循环”的概貌:
根据文章1总体概述,我们可以看到ngx_process_events_and_timers函数之前,进行了很多初始化操作。接下来只是分析事件驱动的核心ngx_process_events_and_timers函数,当然这也是worker进程的核心。
2.几个变量的作用
首先说明几个变量的作用
1)ngx_accept_mutex_held:表示进程当前是否持有锁
2)ngx_accept_mutex_delay:当获得锁失败后,再次去请求锁的间隔时间,这个时间在配置文件中设置
3)NGX_POST_EVENTS标记:设置了这个标记就说明当socket有数据被唤醒时,不会马上accept或者读取,而是将这个事件保存起来,然后当我们释放锁以后,才会进行accept或者读取这个句柄。
4)ngx_posted_accept_events:ngx_event_t数组,暂存epoll从监听套接口wait到的accept事件。该数组不为空,就表示有accept事件发生。
5)ngx_posted_events:ngx_event_t数组,暂存进程中非accept事件。
6)ngx_use_accept_mutex:代表是否使用accept互斥体。默认是使用,accept_mutex off;指令关闭。 accept mutex的作用就是避免惊群,同时实现负载均衡。在配置文件中可以配置。
7)ngx_accept_disabled :用于实现进程关于连接的基本负载均衡。ngx_accept_disabled变量在ngx_event_accept函数中计算。 如果ngx_accept_disabled大于了0,就表示该进程接受的 连接过多,因此就放弃一次争抢accept mutex的机会,同时将 自己减1。然后,继续处理已有连接上的事件。Nginx就借用 此变量实现了进程关于连接的基本负载均衡。
8)ngx_shmtx_t中成员变量fd:进程间共享的文件句柄。通过这个fd来控制进程的互斥。这部分内容在nginx中锁的设计以及惊群的处理有详细说明。
9)dalta:对epoll wait事件的耗时统计,存在毫秒级的耗时就对所有事件的timer进行检查;如果time out就从timer rbtree中删除到期的timer,同时调用相应事件的handler函数完成处理。
3.ngx_process_events_and_timers结构图
4.ngx_process_events_and_timers函数详解
void
ngx_process_events_and_timers( ngx_cycle_t *cycle)
{
ngx_uint_t flags;
ngx_msec_t timer, delta;
if (ngx_timer_resolution) {
timer = NGX_TIMER_INFINITE;
flags = 0;
} else {
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME;
#if (NGX_THREADS)
if (timer == NGX_TIMER_INFINITE || timer > 500) {
timer = 500;
}
#endif
}
if (ngx_use_accept_mutex) { /*默认使用accept mutex*/
if (ngx_accept_disabled > 0) {/*用于进程中连接的基本负载均衡*/
ngx_accept_disabled--;
} else {
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {/*尝试去获取锁,这部分内容在文章9Nginx中accept互斥锁中有详细讲述*/
return;
}
if (ngx_accept_mutex_held) {
/*ngx_accept_mutex_held=1,表示当前进程持有锁,那么所有接收到的accept事件都不会马上处理,而是被保存到ngx_posted_accept_events数组中*/
flags |= NGX_POST_EVENTS;
} else {
/*ngx_accept_mutex_held=0,表示当前进程没有持有锁,那么就设置等待时间,之后继续去抢锁。*/
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}
delta = ngx_current_msec;
/*epoll开始wait事件了,ngx_process_events的具体实现是对应到 epoll模块中的ngx_epoll_process_events函数。单独分析epoll 模块的时候,再具体看看。 */
(void) ngx_process_events(cycle, timer, flags);
delta = ngx_current_msec - delta;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle-> log, 0,
"timer delta: %M", delta);
/*ngx_posted_accept_events:有accept事件被暂存在ngx_posted_accept_events数组中,那么ngx_event_process_posted就处理该数组中的事件
ngx_event_process_posted处理流程:遍历整个数组,先从事件队列中删除事件,然后执行该事件的handler。
*/
if (ngx_posted_accept_events) {
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
}
/*处理完所有accept事件后,如果拥有锁的话,就赶紧释放了,其他进程等着抢呢*/
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}
if (delta) {
/*每次该函数都不断的从红黑树中取出时间值最小的,查看他们是否已经超时,然后执行他们的函数,直到取出的节点的时间没有超时为止*/
ngx_event_expire_timers();
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle-> log, 0,
"posted events %p", ngx_posted_events);
/*处理普通事件(连接上获得的读写事件)队列上的所有事件, 因为每个事件都有自己的handler方法,该怎么处理事件就 依赖于事件的具体handler了。 */
if (ngx_posted_events) {
if (ngx_threaded) {
ngx_wakeup_worker_thread(cycle);
} else {
ngx_event_process_posted(cycle, &ngx_posted_events);
}
}
}
/*为何是在ngx_event_process_posted之后才释放锁呢,*/
这儿解释一下:非常感谢C/C++ 航天灰机的帮助
ngx_event_process_posted(cycle,&ngx_posted_accept_events)中调用ngx_event_accept函数调用ls->handler即ngx_http_init_connection
if (rev->ready) { /* the deferred accept(), rtsig, aio, iocp */ if (ngx_use_accept_mutex) { ngx_post_event(rev, &ngx_posted_events);/*这儿将所有的非accept事件都放入ngx_posted_events数组中,不继续往下执行,等待释放锁以后才继续执行*/ return; } ngx_http_init_request(rev); return; }
ngx_process_events_and_timers一做完工作,就又回到了事件循环中去了,上图示;但会很快又会回到事件处理中来。
5.分析Nginx对accept事件的处理
在上面的讲述中,ngx_http_init_connection函数对于accept的事件会放入ngx_posted_events数组中。在ngx_process_events_and_timers函数中通过 ngx_event_process_posted(cycle, &ngx_posted_events);执行accept事件的处理函数。其处理函数时ngx_event_accept函数,位于src/event/ngx_event_accept.c。
代码分析如下:
void
ngx_event_accept( ngx_event_t *ev)
{
socklen_t socklen;
ngx_err_t err;
ngx_log_t *log;
ngx_uint_t level;
ngx_socket_t s;
ngx_event_t *rev, *wev;
ngx_listening_t *ls;
ngx_connection_t *c, *lc;
ngx_event_conf_t *ecf;
u_char sa[NGX_SOCKADDRLEN];
.................................................................................
lc = ev ->data ;
ls = lc-> listening ;
ev ->ready = 0;
.................................................................................
do {
socklen = NGX_SOCKADDRLEN;
s = accept(lc->fd, ( struct sockaddr *) sa, &socklen); /*accept一个新的连接*/
.................................................................................
/*accept到一个新的连接后,就重新计算ngx_accept_disabled的值 ngx_accept_disabled已经提及过了,它主要用来做负载均衡之用。 这里,我们能够看到它的求值方式是“总连接数的八分之一,减去 剩余的连接数”。总连接数是指每个进程设定的最大连接数,这个数字 可以在配置文件中指定。由此处的计算方式,可以看出:每个进程accept 到总连接数的7/8后,ngx_accept_disabled就大于0了,连接也就 超载了。 */
ngx_accept_disabled = ngx_cycle-> connection_n / 8
- ngx_cycle-> free_connection_n ;
c = ngx_get_connection(s, ev-> log);
.................................................................................
c-> pool = ngx_create_pool(ls->pool_size , ev ->log );
if (c->pool == NULL) {
ngx_close_accepted_connection(c);
return ;
}
c-> sockaddr = ngx_palloc(c->pool , socklen);
if (c->sockaddr == NULL) {
ngx_close_accepted_connection(c);
return ;
}
ngx_memcpy(c-> sockaddr , sa, socklen);
log = ngx_palloc(c-> pool , sizeof ( ngx_log_t));
if (log == NULL) {
ngx_close_accepted_connection(c);
return ;
}
/* set a blocking mode for aio and non-blocking mode for others */
if (ngx_inherited_nonblocking) {
if (ngx_event_flags & NGX_USE_AIO_EVENT) {
if (ngx_blocking(s) == -1) {
ngx_log_error(NGX_LOG_ALERT, ev-> log, ngx_socket_errno,
ngx_blocking_n " failed" );
ngx_close_accepted_connection(c);
return ;
}
}
} else {/*我们使用的epoll模型,在这里设置连接为nonblocking*/
if (!(ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT))) {
if (ngx_nonblocking(s) == -1) {
ngx_log_error(NGX_LOG_ALERT, ev-> log, ngx_socket_errno,
ngx_nonblocking_n " failed" );
ngx_close_accepted_connection(c);
return ;
}
}
}
*log = ls-> log ;
/*初始化新连接*/
c-> recv = ngx_recv;
c-> send = ngx_send;
c-> recv_chain = ngx_recv_chain;
c-> send_chain = ngx_send_chain;
c-> log = log;
c-> pool ->log = log;
c-> socklen = socklen;
c-> listening = ls;
c-> local_sockaddr = ls->sockaddr ;
c-> unexpected_eof = 1;
/*这里的listen handler很重要,它将完成新连接的最后初始化工作 同时将accept到的新连接放入epoll中;挂在这个handler上的函数 就是ngx_http_init_connection(位于src/http/ngx_http_request.c中); 这个函数放在分析http模块的时候再看吧。 */
ls-> handler (c);
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
ev-> available--;
}
} while ( ev-> available);
}
6.小结
来自于http://bollaxu.iteye.com/blog/855457 对于下面红色的语句有些不理解
//Debug追踪
下面,以单进程和多进程处理一个http请求为例,分析一下事件处理的流程。我用nginx里面已有的ngx_log_debugX()来插入事件处理的主要函数ngx_epoll_process_events()和ngx_event_process_posted()。在编译的时候,需要加上"--with-debug"参数。并指定nginx.conf里面的"error_log logs/debug.log debug_core | debug_event | debug_http;"。重新启动nginx。
单进程(work_processes 1):
1. 在初始化即ngx_worker_process_init()中调用两次ngx_epoll_add_event()。第一次是在ngx_event_process_init()里面,即给每个监听的端口(在我的例子里只监听80端口)添加一个NGX_READ_EVENT事件;第二次是ngx_add_channel_event(),即给进程间通行的socketpair添加NGX_READ_EVENT事件。
2. 不断调用ngx_epoll_process_events()函数,探测监听的事件是否发生。如果此时有一个http请求进来,就会触发epoll的事件。由于之前每个监听的端口已经设置handler是ngx_event_accept(),这样,就会在ngx_epoll_process_events()里面调用rev->handler(rev),即调用ngx_event_accept()。在这个函数里,accept()被调用,即接收请求并为其分配一个新的连接,初始化这个新连接,并调用listening socket的handler,即ls->handler(c)。因为ls->handler在http_block()(读取配置之后)里面已经设置了(ls->handler = ngx_http_init_connection;),那么就会调用ngx_http_init_connection()。而在这个函数里,又会添加一个读事件,并设置其处理钩子是ngx_http_init_request()。
3. epoll触发新的事件调用ngx_http_init_request(),并继续http请求处理的每一个环节。(如process request line,process headers,各个phase等)
4. 最后client关闭了连接(我用的是Linux下的curl)。调用了ngx_http_finalize_request() => ngx_http_finalize_connection() => ngx_http_set_keepalive()。ngx_http_set_keepalive()函数设置事件的处理函数是ngx_http_keepalive_handler(),并调用ngx_post_event()把它添加到ngx_posted_events队列里。然后ngx_event_process_posted()函数就会一一处理并删除队列里所有的事件。在ngx_http_keepalive_handler()函数里,调用ngx_http_close_connection() => ngx_close_connection() => ngx_del_conn(c,NGX_CLOSE_EVENT)。ngx_del_conn()即ngx_epoll_del_connection(),即把这个处理请求的connection从epoll监听的事件列表中删除。
多进程(我设置了work_processes 2):和单进程不同,单进程设置epoll timer为-1,即没有事件就一直阻塞在那里,直到监听的端口收到请求。而多进程则不同,每个进程会设置一个epoll_wait()的timeout,去轮番尝试获取在监听端口接受请求的权利,如果没有事件就去处理其它的事件,如果获得了就阻塞(*直到有任意事件发生)
1. 在ngx_event_process_init()里面,只会调用ngx_add_channel_event()给进程间通信的socketpair添加事件,而不给http监听的端口添加事件(为了保证不会有多个工作进程来同时接受请求)。而每个进程被fork()之后,父进程(master process)都会调用ngx_pass_open_channel() => ngx_write_channel() => sendmsg()来通知所有已经存在的进程(这会触发接收方的事件,调用ngx_channel_handler()函数)
2. 在ngx_process_events_and_timers()里,用一个锁来同步所有的进程ngx_trylock_accept_mutex(),并只有一个进程能够得到ngx_accept_mutex这个锁。得到这个锁的进程会调用ngx_enable_accept_events()添加一个监听端口的事件。
3. 在ngx_epoll_process_events()里,调用了ngx_locked_post_event()添加了一个读事件到accept queue(即ngx_posted_accept_events),然后在ngx_event_process_posted()里面处理,即调用ngx_event_accept(),并添加一个读事件(后面和单进程是一样的)。在处理完ngx_posted_accept_events队列里面的所有accept事件之后,ngx_accept_mutex这个锁也会被释放,即把接受请求的权利让给其它的进程。
*在多进程的模式下,每当有新的子进程启动的时候,父进程(master process)都会向其余所有进程的socketpair channel广播新的子进程的channel。这样,就会导致之前获取监听端口权限(即ngx_accept_mutex)的进程触发epoll事件,从而释放ngx_accept_mutex,虽然这个是发生在初始化阶段(之后子进程间一般不通信),一般不会产生两个或多个进程同时在epoll添加监听端口事件的情况。但是在理论上,这样的设计可能会导致系统的bug(比如有人通过给子进程发送信号的办法来实现一些特殊的功能时,就有可能让其中一个进程放弃ngx_accept_mutex,而另外某一个进程在之后先于它再次获取到ngx_accept_mutex)。
- 文章5:Nginx源码分析--事件循环
- 文章5:Nginx源码分析--事件循环
- Nginx源码分析-事件循环
- Nginx源码分析-事件循环
- Nginx源码分析-事件循环
- Nginx源码分析-事件循环
- 分析Nginx源码中的事件循环
- [nginx源码分析]nginx事件逻辑
- 文章16:Nginx变量的源码分析
- 文章3:Nginx源码分析-ngx_list_t单链表
- Nginx源码分析-事件驱动的初始化
- Nginx源码分析--事件驱动的初始化
- Nginx源码分析-事件驱动的初始化
- nginx源码分析--event事件驱动初始化
- nginx源码分析之事件机制
- nginx源码分析--事件模块 & 琐碎
- Nginx源码分析—定时器事件
- nginx源码分析--event事件驱动初始化
- MfC 进度条控件
- java7 NIO2(5) 文件和目录操作API
- FindFirstFile
- iOS Code Signing: 解惑详解
- (转自csdn文章)iPhone系列开发博客资源汇总
- 文章5:Nginx源码分析--事件循环
- (转自:zhuqilin0)iOS开发之缓存(一):内存缓存 .
- cookie实现登陆
- 封装 Windows XP Professional上海政府版 操作系统 教程
- Ragdoll布娃娃组件
- gd图像——验证码
- [转载]各种材料摩擦系数表
- 游戏AI的综合设计
- Objective-c中Extension(延展)的用法,Objective-c中的私有