文档章节

nginx源码分析之事件机制

那一剑的风情
 那一剑的风情
发布于 2012/10/17 11:54
字数 1476
阅读 6971
收藏 52
点赞 3
评论 2
事件机制尤如nginx的心脏一般,不停的运转,保证了nginx的请求响应模式得以正常工作。
本文将剖析事件机制的原理和实现。

nginx本身支持多种机制,如 poll, epoll, select, aio, kqueue等,这里分析epoll,因为这是nginx的杀手锏。
初略接触时,我们大概只知道监听、请求、接受、响应这几个概念。我们沿着这个思维展开,看nginx如何设计这些结构体的。

1、大体上设计
不管是监听,还是请求,只要能产生fd的,都将视为连接,一个fd对应一个连接(connection)。
每个连接都可以读(read)和写(write),这两个都视为事件(event),结构体为:
struct ngx_connection_s {
    void               *data;    // 将要关联的模型,listening, request, ... 或其它
    ngx_event_t        *read;    //  读事件
    ngx_event_t        *write;   //  写事件

    ngx_socket_t        fd;        //  句柄

    ngx_listening_t    *listening;   // 对应的监听
};

struct ngx_event_s {
    void            *data;      //  将要关联的模型,connection, ... 或其它

    unsigned         write:1;   //  是否可写
    
    unsigned         accept:1;  //   是否是accept产生的事件

    unsigned         instance:1; //   避免惊群的一个设计

    unsigned         active:1;   //   是否有效,当加入epoll_ctl时就置为1

    unsigned         ready:1;     //    epoll_wait捕获到时就置为1


    unsigned         timedout:1; //   是否超时
    unsigned         timer_set:1; //   是否置为定时器,即加入超时定时器红黑树时就置为1


    ngx_event_handler_pt  handler; //   事件处理函数,核心


    ngx_rbtree_node_t   timer;        //   加入红黑树时需要的辅助节点
};

2、监听listen
当处理完配置文件解析(针对listen指令)时,nginx开始处理这些listen。将它们放在 ngx_cycle->listening里。
struct ngx_cycle_s {
    ...
    ngx_array_t  listening;  // 是个数组,结构体为ngx_listening_s
    ...
}

struct ngx_listening_s {
    ngx_socket_t        fd;        // 句柄描述符

    struct sockaddr    *sockaddr;
    socklen_t           socklen;    
    ...
};
监听是有读事件,而没有写事件的,epoll有两个模式LT和ET,监听采用LT,监听的read事件的处理函数为ngx_event_accept。

3、接受accept
这个产生的fd,有读和写事件,对读事件的处理函数为ngx_http_init_request。因此一个连接请求一旦发送完,就从这个函数开始执行
这也是request开始的生命周期,这里的结构体为:
struct ngx_http_request_s {
    uint32_t                          signature;         /* "HTTP" */

    ngx_connection_t                 *connection;   // 对应的连接

    /* 这个结构体是非常庞大的,但不复杂,比如它处理了并重新保存了配置文件的上下文 */
    void                            **ctx;
    void                            **main_conf;
    void                            **srv_conf;
    void                            **loc_conf;

    /* 比如请求有关的信息的会保存到它的成员里 */
    u_char                           *uri_start;
    u_char                           *uri_end;
    u_char                           *uri_ext;
    u_char                           *args_start;
    u_char                           *request_start;
    u_char                           *request_end;
    u_char                           *method_end;
    u_char                           *schema_start;
    u_char                           *schema_end;
    u_char                           *host_start;
    u_char                           *host_end;
    u_char                           *port_start;
    u_char                           *port_end;

    unsigned                          http_minor:16;
    unsigned                          http_major:16;
};

4、神奇的超时
因为处理了超时,整个代码的复杂度至少提升了一个档次,像libevent这种东东,它是用信号处理超时的,
nginx作者应该认为这种处理方式不是线程安全的,所以自己实现了一个。这不是重复创造轮子,超时机制
是应用程序的一部分逻辑,在应用程序代码里面实现无可厚非。
超时机制用了红黑树,因为有频繁的插入,查找和删除,用红黑树的效率是非常高的。

初始化:专门的变量ngx_event_timer_rbtree
ngx_event_timer_init(cycle->log);

超时检查:epoll所有事件处理之前,检查一遍哪些是超时的,将event标记为timedout,并且马上执行事件处理
ngx_event_expire_timers(void)
{
    ...
    
    for ( ;; ) {      

        node = ngx_rbtree_min(root, sentinel);

        /* node->key <= ngx_current_time,很简单巧妙的设计,怎么视为超时 */

        if ((ngx_msec_int_t) (node->key - ngx_current_msec) <= 0) {
            ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));

            ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);

            ev->timer_set = 0; // 重置timer_set
            ev->timedout = 1;  // 标记为超时

            ev->handler(ev);   // 马上处理,注意这里没有处理成如果超时就关闭连接,这是由handler自行处理的
                               //  后面会再解释这个设计

            continue;
        }

        break; // 如果没有超时的事件,结束退出
    }
}

5、epoll的应用
一个连接(或事件)它要添加到epoll里,才会被处理,不然即使它可读或可写了,也不会理会。
for ( ;; ) {
    timer = ngx_event_find_timer();

    events = epoll_wait(ep, event_list, (int) nevents, timer);

    ;更新时间

    ;超时处理

    ;正常事件处理
}


事件操作:
ngx_epoll_add_event
ngx_epoll_del_event


6、梳理
如果我们自己写业务逻辑,如何处理一个事件呢?
假设fd已经有了,可能是你通过socket函数产生的。

获取连接:
c = ngx_get_connection(fd);

处理read,write:
c->read->handler = ngx_http_init_request;
c->write->handler = ngx_http_empty_handler;

定时器处理:定时器是针对事件的
ngx_add_timer(c->read, c->listening->post_accept_timeout);
ngx_add_timer(c->write, ...);

注册事件:即加入epoll,这里一般采用ET模式。
ngx_handle_read_event(c->read, 0);
ngx_handle_read_event(c->write, 0);

上面的例子将read,write都处理了,实际情况不一定得这样,看你要不要处理读或写事件,哪个需要,启用哪个。


7、不能忘却的timedout。
如果你用心,你会发现,所有的event的handler函数体的前面都有一段这么代码
ngx_http_init_request(ngx_event_t *rev)
{       
    ...

    if (rev->timedout) {  
        ngx_http_close_connection(c);
        return;
    }

    ...
}


ngx_http_process_request_line(ngx_event_t *rev)
{
    ...

    if (rev->timedout) {       
        c->timedout = 1;
        ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
        return;
    }

    ...
}
所以前面提到,在超时检查时,nginx只是将event标记为timedout,而没有关闭连接,这是因为,nginx可以处理
http, mail或不同的连接,每个连接都有自己不同的处理方式,所以这段代码无处不在,将就咯 -_-


8、你看得懂这段代码吗?
ngx_url_t                u;
ngx_peer_connection_t   peer;

ngx_memzero(&u, sizeof(ngx_url_t));
ngx_memzero(&peer, sizeof(ngx_peer_connection_t));

ngx_str_set(&u.url, "127.0.0.1:8080");

ngx_parse_url(pool, &u);

peer.sockaddr = u.addrs->sockaddr;
peer.socklen = u.addrs->socklen;
peer.name = u.addrs->name;
peer.get = ngx_event_get_peer;

ngx_event_connect_peer(&peer);

peer.connection->read->handler = ngx_mail_auth_http_read_handler;
peer.connection->write->handler = ngx_mail_auth_http_write_handler;

ngx_add_timer(peer.connection->read, ahcf->timeout);
ngx_add_timer(peer.connection->write, ahcf->timeout);

这是截取mail部分auth_http的代码,nginx用的很广的一个就是自己创建socket连接到另一服务器,
像fastcgi, proxy,都是这样,里面的核心就是 ngx_event_connect_peer,这主题比较深,留以后专门分析,抛下砖头先。

ps:写这些文章时,有些代码都是凭经验敲的,有错误之处请指正。能谈到设计上的层次就足矣。

阅读原文:http://nglua.com/reads/4.html


© 著作权归作者所有

共有 人打赏支持
那一剑的风情

那一剑的风情

粉丝 119
博文 20
码字总数 21879
作品 0
厦门
程序员
加载中

评论(2)

ustbgaofan
ustbgaofan
学习了
Silencer
Silencer
受教育
nginx源码分析——事件模块

事件模块概述 事件处理框架所要解决的问题是如何收集,管理,分发事件。这里所说的事件,主要以网络事件和定时器事件为主,而网络事件中又以TCP网络事件为主。由于网络事件与网卡中断处理程序...

hncscwc
2016/06/23
186
0
Android 机制篇 - 事件分发机制超详解(🔥🔥🔥🔥🔥🔥🔥🔥)

Android 虽然不是四大组件,但其并不比四大组件的地位低(涉及面的广度和深入甚至比四大组件还复杂🔥)。而View的核心知识点“事件分发机制”则是不少刚入门同学的拦路虎(1、项目中处处遇...

Pepsimaxin
07/12
0
0
nginx源码分析——定时器

概述 nginx实现了自己的定时器触发机制,它与epoll等事件驱动模块处理的网络事件不同;在网络事件中,网络事件的触发是由内核完成的,而定时器事件则完全是由nginx自身实现的,它与内核完全无...

hncscwc
2016/10/31
97
0
一步步探索学习Android Touch事件分发传递机制(三)

前言 该系列文章分三篇: 一步步探索学习Android Touch事件分发传递机制(一) 通过写demo打Log,以ACTIONDOWN事件为例,完整了解整个Android Touch事件分发传递机制。 一步步探索学习Andro...

单人旅途alan
2017/12/04
0
0
Nginx fastcgi cache机制的疑问

最近在使用Nginx+php-cgi,对于Nginx fastcgi cache的机制有点疑问。 Nginx配置了fastcgi cache之后,到底是谁在做cache控制(就是cache是谁生成的,谁去读取的)?是Nginx还是php-cgi? 实验...

KellyKuang
2017/03/20
145
1
如何测试Nginx的高性能

简介 Nginx ("engine x") 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP代理服务器; 作为一款轻量级的Web服务器,具有占有内存少,并发能力强等优势,是高连接并发场景下Apa...

xxrenzhe11
2014/05/01
0
0
收藏的博客 -- 高性能Linux服务器

http://zhuanlan.51cto.com/columnlist/shenj/ --- 58沈剑 http://blog.csdn.net/analogouslove --- 范蠡&张小方 http://blog.csdn.net/column/details/15700.html --- teamtalk https://gi......

libaineu2004
2017/08/08
0
0
libevent源码深度剖析

作者:http://blog.csdn.net/sparkliang/article/category/660506 libevent源码深度剖析十三——libevent信号处理注意点 2010-02-11 20:00阅读(1669)评论(6) libevent源码深度剖析PDF 2010-...

晨曦之光
2012/03/09
2.4K
0
nginx源码分析——http处理流程

http处理流程概述 nginx对于http请求处理的大概流程是:接收客户端发起的连接,然后接收并解析http请求行,接收并解析http头部;再根据配置文件nginx.conf找到相关http模块,使这些模块依次合...

hncscwc
2016/07/15
269
2
关于CoordinatorLayout AppBarLayout原理的一些分析

这几天学了一些CoordinatorLayout、AppBarLayout配合使用的一些方法,之前还写了一篇CoordinatorLayout Behavior一些笔记,通过这几天对源码的阅读,现在对CoordinatorLayout、AppBarLayout这...

HumorousMan
2017/06/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

CDH的坑之Sqoop导出数据到MySQL

CDH的坑之Sqoop导出数据到MySQL 最近使用Sqoop从Hive导出数据到MySQL中,出现了一系列的问题,下面将这个问题记录一下,避免再度踩坑! 导出语句 sqoop export --connect jdbc:mysql://192....

星汉
12分钟前
0
0
Hyperledger Fabric 客户端开发三

前面两篇文章介绍了Hyperledger Fabric SDK并使用一个实例介绍如何通过SDK和Hyperledger Fabric Blockchain交互, 现在详细分析相关的过程。 首先看 enroll (登录) admin 过程。 'use stric...

十一月不远
12分钟前
0
0
PowerDesigner连接MySQL和逆向工程图

最近想梳理公司项目的表间关系,从项目后台管理系统的操作入手,以及代码的hibernate注解入手,都不算特别尽人意,于是最后还是鼓捣了一下PowerDesigner的逆向工程图,这样更直观一些。 想着...

Oo若离oO
13分钟前
0
0
威胁web应用安全的错误

一般绝大部分的web应用攻击都是没特定目标的大范围漏洞扫描,只有少数攻击确实是为入侵特定目标而进行的针对性尝试。这两种攻击都非常频繁,难以准确检测出来,许多网站的web应用防火墙都无法...

上树的熊
15分钟前
2
0
pypy2 install crypto error

install pycryptodome instead pip install pycryptodome

coord
19分钟前
0
0
Service Mesh所应对的8项挑战

Lori Macvittie 微服务架构是把双刃剑,我们享受它带来的开发速度(development velocity),却也不得不面对服务间通讯带来的复杂性问题。 目前大多数扩展容器化微服务的架构多是基于proxy-b...

好雨云帮
28分钟前
0
0
时间复杂度

1. 维基上的定义 在计算机科学中,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低...

liuyan_lc
34分钟前
0
0
js中的~符

~是js里的按位取反操作符,~~就是执行两次按位取反,其实就是保持原值,但是注意虽然是原值,但是对布尔型变量执行这个操作,会转化成相应的数值型变量,也就是 ~~true === 1,~~false === 0...

JamesView
35分钟前
0
0
webpack安装

npm install --save-dev webpack-cli

Vincent-Duan
37分钟前
0
0
实时监听EditText内容变化

主要是addTextChangedListener方法的使用 aswerEdittext.addTextChangedListener(new TextWatcher() { //编辑框的内容发生改变之前的回调方法 @Override public void before...

王先森oO
41分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部