首先事件循环的起点就是监听端口获取连接,我们可以在ngx_event_core_module模块的ngx_event_process_init函数中看到如下的代码

  1. /* for each listening socket */
  2. /*为每个监听套接字从connection数组中分配一个连接,即一个slot*/
  3. ls = cycle->listening.elts; //监听套接字是在master进程那里继承过来的,已经初始化好了
  4. for (i = ; i < cycle->listening.nelts; i++) {
  5. //为当前监听套接字的文件描述符分配一个connection,函数返回值c是当前监听套接字关联的connection
  6. c = ngx_get_connection(ls[i].fd, cycle->log);
  7.  
  8. if (c == NULL) {
  9. return NGX_ERROR;
  10. }
  11.  
  12. c->log = &ls[i].log;
  13.  
  14. c->listening = &ls[i]; //当前连接的监听端口
  15. ls[i].connection = c; //当前监听端口的connection
  16.  
  17. rev = c->read; //rev指向当前connection的读事件
  18.  
  19. rev->log = c->log;
  20. rev->accept = ; //表示当前的读事件是监听端口的accept事件,可以用于epoll区分是一般的读事件还是监听对口的accept事件
  21.  
  22. #if (NGX_HAVE_DEFERRED_ACCEPT)
  23. rev->deferred_accept = ls[i].deferred_accept;
  24. #endif
  25. if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
  26. if (ls[i].previous) {
  27. /*
  28. * delete the old accept events that were bound to
  29. * the old cycle read events array
  30. */
  31.  
  32. old = ls[i].previous->connection;
  33.  
  34. if (ngx_del_event(old->read, NGX_READ_EVENT, NGX_CLOSE_EVENT)
  35. == NGX_ERROR)
  36. {
  37. return NGX_ERROR;
  38. }
  39.  
  40. old->fd = (ngx_socket_t) -;
  41. }
  42. }
  43.  
  44. /*注册监听套接口读事件的回调函数ngx_event_accept*/
  45. rev->handler = ngx_event_accept; //说白了就是从监听套接字来获取连接的socket

这部分代码在worker进程中,为每一个listening分配一个connection与之对应,并将该connection的读事件的处理函数设置为ngx_event_accept函数,也就是说用这个函数来处理listening的accept,好了接下来我们从这个函数看起(该函数定义在Ngx_event_accept.c):

  1. lc = ev->data; //获取该事件对应的connection
  2. ls = lc->listening;
  3. ev->ready = ;
  4.  
  5.   //因为是用的epoll的触发机制,所以这里要不断的循环,直到数据全部读取完了才行
  6. do {
  7. socklen = NGX_SOCKADDRLEN;
  8.  
  9.   //调用accept函数来获取连接的socket
  10. s = accept(lc->fd, (struct sockaddr *) sa, &socklen);

上部分的代码,首先从event变量中获取该事件对应的connection,接着就可以调用accept函数了,从监听的socket描述符中获取连接。

  1. /*accept到一个新的连接后,就重新计算ngx_accept_disabled的值
  2. ngx_accept_disabled已经提及过了,它主要用来做负载均衡之用。
  3.  
  4. 这里,我们能够看到它的求值方式是“总连接数的八分之一,减去
  5. 剩余的连接数”。总连接数是指每个进程设定的最大连接数,这个数字
  6. 可以在配置文件中指定。由此处的计算方式,可以看出:每个进程accept
  7. 到总连接数的7/8后,ngx_accept_disabled就大于0了,连接也就
  8. 超载了。
  9. */
  10.  
  11. ngx_accept_disabled = ngx_cycle->connection_n /
  12. - ngx_cycle->free_connection_n;
  13. //为刚刚连接的socket分配connection
  14. c = ngx_get_connection(s, ev->log);

上述代码用于在获取连接之后,计算ngx_accept_disabled的值,它用来进行worker进程间的负载均衡,避免一个worker进程持有太多的connection,具体的以后会讲。然后就是为连接进来的socket描述符分配connection,接下来的代码就是初始化这个刚刚分配的connection,例如为其分配内存池,将socket描述符设置为非阻塞等等。

  1.      //将当前新生成的连接加入
  2. if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == ) {
  3. if (ngx_add_conn(c) == NGX_ERROR) {
  4. ngx_close_accepted_connection(c);
  5. return;
  6. }
  7. }
  8.  
  9. log->data = NULL;
  10. log->handler = NULL;
  11.  
  12. /*这里的listen handler很重要,它将完成新连接的最后初始化工作
  13. 同时将accept到的新连接放入epoll中;挂在这个handler上的函数
  14. 就是ngx_http_init_connection(位于src/http/ngx_http_request.c中);
  15. 这个函数放在分析http模块的时候再看吧。
  16. */
  17. ls->handler(c);

上面这部分代码是比较重要的,它首先调用ngx_add_conn函数将刚刚的连接加入到epoll当中去,我们可以看看ngx_add_conn的定义,在Ngx_event.h当中:

  1. #define ngx_add_conn ngx_event_actions.add_conn

其实这里看过前面的文章就会知道ngx_add_conn说白了就是调用实际事件模块的add_conn函数,如果实际使用的是epoll模块的话那么将会调用epoll模块的ngx_epoll_add_connection函数,接下来还有一句代码:

  1. ls->handler(c);

这里就是用listening的handler对刚刚分配的connection进行处理,这里就会涉及到http部分的东西了,以后再说吧。

好了到这里ngx_event_accept函数说的就差不多了。接下来可以正式进入Nginx的事件循环了。我们先看事件循环的入口吧,在worker进程的执行函数ngx_worker_process_cycle中,有如此一句代码在每次循环中都会用到

  1. //处理时间和定时,说白了这个函数不断的处理发生的事件
  2. ngx_process_events_and_timers(cycle);

嗯,ngx_process_events_and_timers函数就是事件循环的入口函数,其定义在Ngx_event.c当中,接下来我们来分析该函数:

  1. /*ngx_use_accept_mutex变量代表是否使用accept互斥体
  2. 默认是使用,accept_mutex off;指令关闭。
  3. accept mutex的作用就是避免惊群,同时实现负载均衡。
  4. */
  5. if (ngx_use_accept_mutex) {
  6. if (ngx_accept_disabled > ) {
  7. ngx_accept_disabled--;
  8. } else {
  9. /* ngx_accept_disabled小于0,连接数没超载*/
  10.  
  11. /*尝试锁accept mutex,只有成功获取锁的进程,才会将listen
  12. 套接字放入epoll中。因此,这就保证了只有一个进程拥有
  13. 监听套接口,故所有进程阻塞在epoll_wait时,不会出现惊群现象。
  14. */
  15. //这里的ngx_trylock_accept_mutex函数中,如果顺利的获取了锁,那么它会将监听端口注册到当前worker进程的epoll当中
  16. if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
  17. return;
  18. }
  19. /*获取锁的进程,将添加一个NGX_POST_EVENTS标志,
  20. 此标志的作用是将所有产生的事件放入一个队列中,
  21. 等释放锁后,再慢慢来处理事件。因为,处理事件可能
  22. 会很耗时,如果不先释放锁再处理的话,该进程就长
  23. 时间霸占了锁,导致其他进程无法获取锁,这样accept
  24. 的效率就低了。
  25. */
  26. if (ngx_accept_mutex_held) {
  27. flags |= NGX_POST_EVENTS; //获取了锁,那么就设置标志位
  28. } else {
  29. /*没有获得锁的进程,当然不需要NGX_POST_EVENTS标志了。
  30. 但需要设置最长延迟多久,再次去争抢锁。
  31. */
  32. if (timer == NGX_TIMER_INFINITE
  33. || timer > ngx_accept_mutex_delay)
  34. {
  35. timer = ngx_accept_mutex_delay;
  36. }
  37. }
  38. }
  39. }

首先是判断是否使用了ngx_use_accept_mutex信号量,该信号量用于避免惊群的发生。只有当当前worker进程获取了该信号量之后才会将listening真正加入到自己的epoll当中,相应accept事件。这里也看到了上面提到的ngx_accept_disabled便用的作用,他也用于判断是否将listening加入到当前worker进程的epoll当中,这样可以做到负载均衡,避免一个worker进程持有了太多的connection。

  1. /*epoll开始wait事件了,ngx_process_events的具体实现是对应到
  2. epoll模块中的ngx_epoll_process_events函数。单独分析epoll
  3. 模块的时候,再具体看看。
  4. */
  5. (void) ngx_process_events(cycle, timer, flags);

这句代码直接调用的是实际事件模块的process_events函数,来处理事件,还是来看其定义吧:

  1. #define ngx_process_events ngx_event_actions.process_events

嗯,一看就明白了,如果使用的是epoll模块的话,那么将会调用其的ngx_epoll_process_events函数。待会再细讲它吧。

  1. if (ngx_posted_accept_events) {
  2. /*ngx_posted_accept_events是一个事件队列
  3. 暂存epoll从监听套接口wait到的accept事件。
  4. 前文提到的NGX_POST_EVENTS标志被使用后,就会将
  5. 所有的accept事件暂存到这个队列。
  6.  
  7. 这里完成对队列中的accept事件的处理,实际就是调用
  8. ngx_event_accept函数来获取一个新的连接,然后放入
  9. epoll中。
  10. */
  11. ngx_event_process_posted(cycle, &ngx_posted_accept_events);
  12. }
  13.  
  14. /*所有accept事件处理完成,如果拥有锁的话,就赶紧释放了。
  15. 其他进程还等着抢了。
  16. */
  17. if (ngx_accept_mutex_held) {
  18. ngx_shmtx_unlock(&ngx_accept_mutex);
  19. }

该部分代码判断是否从listening监听中获取了accept事件,如果有的话,那么就要赶紧处理它,因为说明现在worker进程已经占有了ngx_accept_mutex信号量,处理完accept事件后就要赶紧释放掉该信号量,好让别的worker进程可以获取该锁,然后从listening中获取连接。

  1. /*处理普通事件(连接上获得的读写事件)队列上的所有事件,
  2. 因为每个事件都有自己的handler方法,该怎么处理事件就
  3. 依赖于事件的具体handler了。
  4. */
  5. if (ngx_posted_events) {
  6. if (ngx_threaded) {
  7. ngx_wakeup_worker_thread(cycle);
  8.  
  9. } else {
  10. ngx_event_process_posted(cycle, &ngx_posted_events);
  11. }
  12. }

这部分就用于处理普通的事件了。这样ngx_process_events_and_timers函数中处理事件的部分就讲完了,但是该函数其实还有用于处理定时的部分,这个以后讲Nginx的定时函数处理的时候再说吧。

好,接下来分析感刚刚提到的epoll模块的ngx_epoll_process_events函数。

  1. //这里是epoll的wait,将得到的事件存到event_list里面,最大的事件量是nevents
  2. /*一开始就是等待事件,最长等待时间为timer;nginx为事件
  3. 专门用红黑树维护了一个计时器。后续对这个timer单独分析。
  4. */
  5. events = epoll_wait(ep, event_list, (int) nevents, timer); //这个超时事件是从红黑树里面获取的,当前最近的超时,这样可以保证epoll的wait能够在合适的时间内返回,保证定义的超时事件可以执行

首先就是调用epoll_wait函数从epoll中获取发生的事件,然后就可以遍历这些事件了:

  1. //循环遍历所有产生的事件
  2. for (i = ; i < events; i++) {
  3. c = event_list[i].data.ptr; //获取该事件实际对应的connection
  4.  
  5. //instance 说白了就是个整形的变量
  6. instance = (uintptr_t) c & ;
  7. c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~);
  8.  
  9. rev = c->read;
  10.  
  11. if (c->fd == - || rev->instance != instance) {
  12. /*
  13. * the stale event from a file descriptor
  14. * that was just closed in this iteration
  15. */
  16. ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, ,
  17. "epoll: stale event %p", c);
  18. continue;
  19. }
  20.   //获取发生的事件的类型
  21. revents = event_list[i].events;
  22.  
  23. ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, ,
  24. "epoll: fd:%d ev:%04XD d:%p",
  25. c->fd, revents, event_list[i].data.ptr);
  26. //如果发生了错误事件
  27. if (revents & (EPOLLERR|EPOLLHUP)) {
  28. ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, ,
  29. "epoll_wait() error on fd:%d ev:%04XD",
  30. c->fd, revents);
  31. }
  32.  
  33. #if 0
  34. if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
  35. ngx_log_error(NGX_LOG_ALERT, cycle->log, ,
  36. "strange epoll_wait() events fd:%d ev:%04XD",
  37. c->fd, revents);
  38. }
  39. #endif
  40.  
  41. if ((revents & (EPOLLERR|EPOLLHUP))
  42. && (revents & (EPOLLIN|EPOLLOUT)) == )
  43. {
  44. /*
  45. * if the error events were returned without EPOLLIN or EPOLLOUT,
  46. * then add these flags to handle the events at least in one
  47. * active handler
  48. */
  49.  
  50. revents |= EPOLLIN|EPOLLOUT;
  51. }
  52. /*该事件是一个读事件,并该连接上注册的读事件是active的*/
  53. if ((revents & EPOLLIN) && rev->active) {
  54.  
  55. if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
  56. rev->posted_ready = ;
  57.  
  58. } else {
  59. rev->ready = ;
  60. }
  61.  
  62. if (flags & NGX_POST_EVENTS) {
  63. //如果设置了NGX_POST_EVENTS,表示当前worker进程已经获取了锁,那么将获取的事件入队,因为可能是监听端口的accept事件,这里如果是监听端口的accept事件的话,那么该event的accept域会置为1 ,这个是在事件模块的worker进程初始化中会设置的
  64. //这里持有了锁就应该将产生的事件放入队列中,是为了能够在锁释放了以后再处理这些事件,这样可以让别的worker进程能够尽快的获取锁
  65. queue = (ngx_event_t **) (rev->accept ?
  66. &ngx_posted_accept_events : &ngx_posted_events);
  67.  
  68. ngx_locked_post_event(rev, queue);
  69.  
  70. } else {
  71. rev->handler(rev);
  72. }
  73. }
  74.  
  75. wev = c->write;
  76. //如果是写事件,而且相应connection的写事件是激活的
  77. if ((revents & EPOLLOUT) && wev->active) {
  78.  
  79. if (c->fd == - || wev->instance != instance) {
  80.  
  81. /*
  82. * the stale event from a file descriptor
  83. * that was just closed in this iteration
  84. */
  85.  
  86. ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, ,
  87. "epoll: stale event %p", c);
  88. continue;
  89. }
  90.  
  91. if (flags & NGX_POST_THREAD_EVENTS) {
  92. wev->posted_ready = ;
  93.  
  94. } else {
  95. wev->ready = ;
  96. }
  97.  
  98. if (flags & NGX_POST_EVENTS) {
  99. ngx_locked_post_event(wev, &ngx_posted_events);
  100.  
  101. } else {
  102. wev->handler(wev);
  103. }
  104. }
  105. }

上面的代码其实注释就已经说的很明白了,循环遍历所有的事件,判断该事件的类型,并获取该事件实际所属的connection,如果发生的是读事件,那么取出该connection的read事件,然后用read的handler来处理,如果是写事件,那么就获取该connection的write,然后用write的handler来处理。但是这里需要注意的是,

  1. if (flags & NGX_POST_EVENTS) {
  2. //如果设置了NGX_POST_EVENTS,表示当前worker进程已经获取了锁,那么将获取的事件入队,因为可能是监听端口的accept事件,这里如果是监听端口的accept事件的话,那么该event的accept域会置为1 ,这个是在事件模块的worker进程初始化中会设置的
  3. //这里持有了锁就应该将产生的事件放入队列中,是为了能够在锁释放了以后再处理这些事件,这样可以让别的worker进程能够尽快的获取锁
  4. queue = (ngx_event_t **) (rev->accept ?
  5. &ngx_posted_accept_events : &ngx_posted_events);
  6.  
  7. ngx_locked_post_event(rev, queue);
  8.  
  9. } else {
  10. rev->handler(rev);
  11. }

该段代码判断是否持有了信号量,前面已经说过了,如果持有的话,就要将这些事件放入到队列中,稍后在处理,这里是为了尽快能够释放信号量,并且还要判断该事件的类型,区分是accept事件还是普通的读事件,用于将它们放入不同的队列,嗯,event的accept域这个在以前已经说过了,就是为了这个判断的。

好了,ngx_epoll_process_events函数也已经基本讲完了,那么事件循环也就差不多了。

转自:http://www.xuebuyuan.com/2041521.html

Nginx的事件循环的更多相关文章

  1. Node.js:创建应用+回调函数(阻塞/非阻塞)+事件循环

    一.创建应用 如果我们使用PHP来编写后端的代码时,需要Apache 或者 Nginx 的HTTP 服务器,并配上 mod_php5 模块和php-cgi.从这个角度看,整个"接收 HTTP ...

  2. 理解 node.js 的事件循环

    node.js 的第一个基本观点是,I/O 操作是昂贵的: 目前的编程技术最大的浪费来自等待 I/O 操作的完成.有几种方法可以解决这些对性能的影响(来自Sam Rushing): 同步:依次处理单个 ...

  3. nginx worker进程循环

    worker进程启动后,其首先会初始化自身运行所需要的环境,然后会进入一个循环,在该循环中不断检查是否有需要执行的事件,然后处理事件.在这个过程中,worker进程也是需要与master进程交互的,更 ...

  4. JavaScript单线程和浏览器事件循环简述

    JavaScript单线程 在上篇博客<Promise的前世今生和妙用技巧>的开篇中,我们曾简述了JavaScript的单线程机制和浏览器的事件模型.应很多网友的回复,在这篇文章中将继续展 ...

  5. js: 从setTimeout说事件循环模型

    一.从setTimeout说起 setTimeout()方法不是ecmascript规范定义的内容,而是属于BOM提供的功能.查看w3school对setTimeout()方法的定义,setTimeo ...

  6. Javascript并发模型和事件循环

    Javascript并发模型和事件循环 JavaScript的"并发模型"是基于事件循环的,这个并发模型有别于Java的多线程, javascript的并发是单线程的. Javas ...

  7. Node.js 事件循环

    Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...

  8. JavaScript:彻底理解同步、异步和事件循环(Event Loop) (转)

    原文出处:https://segmentfault.com/a/1190000004322358 一. 单线程 我们常说"JavaScript是单线程的". 所谓单线程,是指在JS ...

  9. nodejs系列(二)REPL交互解释 事件循环

    一.REPL交互解释 命令行中输入node启动REPL: > var x =2;undefined> do{x++;... console.log("x:="+x);. ...

随机推荐

  1. 创建OData Service(基于ASP.NET 4.6.1, EF 6),Part I:Project initialize

    由于ASP.NET Core 1处于RC阶段,加上OData WebAPI 对ASP.NET Core 1的跟进不是很积极,基于ASP.NET Core 1的Alpha 1版本已经N月没有check ...

  2. PHP实现日历签到,并实现累计积分功能

    在网站开发过程中我们会经常用到签到功能来奖励用户积分,或者做一些其他活动.这次项目开发过程中做了日历签到,因为没有经验所有走了很多弯路,再次记录过程和步骤. 1.日历签到样式:使用的是calendar ...

  3. hdu 1269 迷宫城堡 (tarjan)

    迷宫城堡Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submiss ...

  4. CSS如何设置列表样式属性,看这篇文章就够用了

    列表样式属性 在HTML中有2种列表.无序列表和有序列表,在工作中无序列表比较常用,无序列表就是ul标签和li标签组合成的称之为无序列表,那什么是有序列表呢?就是ol标签和li标签组合成的称之为有序列 ...

  5. 官网例子,mt-field password获取不到

    新尝试了Mint-UI,在使用表单组件Field时, 直接从demo中拷贝了如下代码: <mt-field label="username" placeholder=&quo ...

  6. 20191107-10 beta发布

    此作业要求参见https://edu.cnblogs.com/campus/nenu/2019fall/homework/9962 1.视频地址:https://v.youku.com/v_show/ ...

  7. 攻防世界 4-ReeHY-main

    检查保护机制: 发现  可以好像写got 然后 程序流程 这里  有double free 然后 再发现 这里很有趣 ,要是我的content为零了 且size 小于112 那就从栈上copy一些内容 ...

  8. OpenStack集成ceph

    openstack组件集成ceph OpenStack集成ceph详细过程可以查看ceph官方文档:ceph document OpenStack Queens版本,1台控制节点controller, ...

  9. .NET高级特性-Emit(2.2)属性

    关于Emit的博客已经进入第四篇,在读本篇博文之前,我希望读者能先仔细回顾博主之前所编写的关于Emit的博文,从该篇博文开始,我们就可以真正的使用Emit,并把知识转化为实战,我也会把之前的博文链接放 ...

  10. Java 大小端转换(基于ByteBuffer)

    大小端的基础知识: 小端( little-endian):低位字节在前,高位字节在后.大端( Big-Endian),则反之.具体而言,就是为了说清楚,CPU架构1 字(word)中byte的存储顺序 ...