Nginx event 模块分析

ngx_event_core_module 模块:
    上下文是: 
     typedef struct {
    ngx_str_t              *name;
    void                 *(*create_conf)(ngx_cycle_t *cycle);
    char                 *(*init_conf)(ngx_cycle_t *cycle, void *conf);
    ngx_event_actions_t     actions;    //定义了IO模型(epoll, poll, select)的各种抽象接口

} ngx_event_module_t;
    ngx_event_core_module的actions全为空,ngx_event_epoll_module的actions才是干活的!

init_module钩子——ngx_event_module_init:master进程中调用
    初始化共享内存。里面包含一个accept互斥锁,还有其他的一些状态计数,都是全局变量。

init_process钩子——ngx_event_process_init:worker子进程初始化时调用
    定时器初始化(红黑树)
    调用每个event模块的module->actions.init实际只有一个ngx_epoll_module——调用ngx_epoll_init
    创建 cycle->connections,cycle->read_events,cycle->write_events,并初始化;
    为每一个监听套接字分配一个connection槽位,设置accept读事件的handlerngx_event_accept。
    如果不使用accept锁,将套接字加入事件监听中(epoll),如果设置NGX_USE_RTSIG_EVENT,调用ngx_add_conn,否则调用ngx_add_event。 不懂其中的区别
否则,需要在 ngx_process_events_and_timers中竞争获得锁之后再添加监听。

ngx_epoll_module模块,没有init_module/init_process钩子,只有干活的actions
     ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */
    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */     // add_connection与add_event有什么区别
        ngx_epoll_del_connection,        /* delete an connection */
        NULL,                            /* process the changes */
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
//actions 成员变量
};


    ngx_epoll_init:
        1、创建全局epoll句柄、event_list
        2、设置 ngx_event_actions = ngx_epoll_module_ctx.actions;以后worker进程都是使用ngx_event_actions 全局变量来进行事件处理。
            ngx_event_actions 抽象了IO事件模型(epoll,poll,select,kqueue等的共同接口)

ngx_process_events_and_timers():里面的东西很重要!
ngx_use_accept_mutex:是否使用锁,当使用锁时,会重新判断是否参加accept锁竞争。
ngx_accept_disabled:如果>0,自减1,不参与accept锁的竞争,直接处理当前connection套接字的读写事件。
        //ngx_event_accept函数,得到一个新的connection套接字
        ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
ngx_accept_mutex_held:是否hold锁,如果得到锁,将产生NGX_POST_EVENTS标志——将所有产生的事件放入到一个队列中。等释放锁以后再慢慢来处理事件。如果没有抢到锁,则处理当前connection套接字的读写请求,等待一定时间再去争抢accept锁。
    如果得到锁,ngx_process_events函数仅仅把IO事件存入队列中,等释放了accept锁之后再处理accept事件和connection套接字的读写事件,避免长久持有锁。

    1、确定epoll_wait的最长等待时间;
    2、如果使用accept互斥锁:
            如果ngx_accept_disabled>0,自减少,进入3中;
            否则,竞争accept互斥锁ngx_trylock_accept_mutex,如果得到accept锁,把监听套接字加入epoll中监听,并设置NGX_POST_EVENTS标志;如果没有得到accept锁,调用ngx_disable_accept_events把accept-fd从epoll中删除;重新设置epoll_wait最长等待时间
    3、ngx_process_events:得到事件,放入队列中(有锁)或直接处理(没有锁)。
    4、处理得到的所有的accept事件(产生新的connection套接字)
    5、如果得到accept锁,释放。
    6、处理定时器的超时事件。
    7、处理队列中connection套接字的读写事件。

ngx_epoll_process_events()
    1、events = epoll_wait(ep, event_list, (int) nevents, timer);等待最长时间time
     2、如果设置NGX_POST_EVENTS放入队列(accept_event队列或是普通队列,因为accept事件要在释放锁之前处理完)中,否则直接处理。
ngx_event_t 中的instance、active、ready成员不知道是什么意思

ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
active成员表示是否在epoll监听中。

监听套接字加入epoll中: event_core模块的init_process钩子ngx_event_process_init
        rev = c->read;        
        rev->log = c->log;

        rev->accept = 1;
1、如果没有使用accept锁,直接加入epoll中
        ngx_add_event(rev, NGX_READ_EVENT, 0)     //这个函数看不透。把监听套接字读事件加入epoll中,并设置读事件的active标志。
2、如果使用accept锁:ngx_process_events_and_timers--》ngx_trylock_accept_mutex--》 ngx_enable_accept_events
          c = ls[i].connection;
        ngx_add_event(c->read, NGX_READ_EVENT, 0)

监听套接字从epoll中删除:
        ngx_trylock_accept_mutex,竞争锁失败,调用ngx_disable_accept_events把accept-fd从epoll中删除。

3、监听套接字事件处理——读: ngx_event_accept函数
        调用accept得到连接套接字,为其分配一个connection_t槽位,并初始化connection_t对象: 
        c->recv = ngx_recv;
        c->send = ngx_send;
        c->recv_chain = ngx_recv_chain;
        c->send_chain = ngx_send_chain;
    并调用
ngx_http_init_connection初始化connection_t对象,并加入定时器中。
        rev = c->read;
        rev->handler = ngx_http_init_request;                //设置读事件初始handler
        c->write->handler = ngx_http_empty_handler;
    调用ngx_epoll_add_connection()监听连接套接字的读写事件。
        ee.events = EPOLLIN|EPOLLOUT|EPOLLET;
        ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);
        if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) { }
        c->read->active = 1;
        c->write->active = 1;


4、连接套接字加入epoll中:ngx_event_accept函数中
连接套接字从epoll中删除:

5、连接套接字的读写事件处理:
        ngx_event_accept-》ngx_http_init_connection中设置read/write handler
ngx_http_init_request() :设置连接套接字读事件的处理函数为ngx_http_process_request_line,负责读取完所有的请求数据。

ngx_http_process_request_line:连接套接字读事件处理函数,解析请求行
    (a) 读事件是超时事件,关闭连接
    (b) 循环体内(循环读取缓存区的数据,并分析):
                b1: ngx_http_read_request_header读取一片数据: 先调用ngx_unix_recv, 再调用ngx_handle_read_event(重新把读事件注册到epoll中,每次epoll_wait后,fd的事件类型将会清空,需要再次注册读写事件。但是我不知道哪里设置了active ==0 
                b2: ngx_http_parse_request_line()里面的内容:解析读到的一片数据
                b3: ngx_http_parse_request_line函数返回了错误,则直接给客户端返回400错误; 如果返回NGX_AGAIN,则需要判断一下是否是由于缓冲区空间不够,还是已读数据不够。如果是缓冲区大小不够了,nginx会调用ngx_http_alloc_large_header_buffer函数来分配另一块大缓冲区,如果大缓冲区还不够装下整个请求行,nginx则会返回414错误给客户端,否则分配了更大的缓冲区并拷贝之前的数据之后,继续调用ngx_http_read_request_header函数读取数据来进入请求行自动机处理,直到请求行解析结束;            
                如果返回了NGX_OK,则表示请求行被正确的解析出来了,这时先记录好请求行的起始地址以及长度,并将请求uri的path和参数部分保存在请求结构的uri字段,请求方法起始位置和长度保存在method_name字段,http版本起始位置和长度记录在http_protocol字段。还要从uri中解析出参数以及请求资源的拓展名,分别保存在args和exten字段。 
            rev->handler = ngx_http_process_request_headers;    //设置连接套接字的读事件处理函数
            ngx_http_process_request_headers(rev);

ngx_http_process_request_headers:连接套接字读事件处理函数,解析请求头域
            先调用了ngx_http_read_request_header()函数读取数据,如果当前连接并没有数据过来,再直接返回,等待下一次读事件到来,如果读到了一些数据则调用ngx_http_parse_header_line()函数来解析,同样的该解析函数实现为一个有限状态机,逻辑很简单,只是根据http协议来解析请求头,每次调用该函数最多解析出一个请求头,该函数返回4种不同返回值,表示不同解析结果。
             返回NGX_OK,表示解析出了一行请求头
            返回NGX_AGAIN,表示当前接收到的数据不够,一行请求头还未结束,需要继续下一轮循环。
             返回NGX_HTTP_PARSE_INVALID_HEADER,表示请求头解析过程中遇到错误,一般为客户端发送了不符合协议规范的头部,此时nginx返回400错误;
       返回NGX_HTTP_PARSE_HEADER_DONE,表示所有请求头已经成功的解析,调用                                                                                                       rc = ngx_http_process_request_header(r);
            if (rc != NGX_OK) {
                return;
            }

            ngx_http_process_request(r);   //后面就要进入请求处理阶段了。


ngx_int_t
ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
{
    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {     //边缘触发

        /* kqueue, epoll */
        if (!rev->active && !rev->ready) {         //ready==0 数据读完了EAGAIN  active ==0 哪里设置了 
//就目前发现只用del_conn/del_event会设置active为0,但实际上不用显示调用del_event不理解
            if (ngx_add_event(rev, NGX_READ_EVENT, NGX_CLEAR_EVENT)  //NGX_CLEAR_EVENT  == EPOLLET
                == NGX_ERROR)
            {
                return NGX_ERROR;
            }
        }
        return NGX_OK;
    } 



epoll 资料:


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值