事件模型

ngx_event_module模块的解析可以类比ngx_http_module
.ngx_event_module模块中的一个ngx_commands_t数组只有一个对应得结构体,名字是event,对应得set方法是ngx_events_block
,该模块负责驱动整个事件模块的解析和初始化,而ngx_event_core_module对events块大部分指令的进行解析以及保存
1.ngx_events_block
1》更新ctx_index的值
ctx = ngx_pcalloc(cf->pool, sizeof(void *));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}

ctx = ngx_pcalloc(cf->pool, ngx_event_max_module sizeof(void *));
if (*ctx == NULL) {
return NGX_CONF_ERROR;
}

/**
* 将在ngx_cycle->conf_ctx数组中存放的ngx_events_module的config信息赋值为ctx
* 也就是所有event module配置信息的数组。
*/
(void *) conf = ctx;
//注意传入的conf是一个二级指针,所以这里还是相当于给conf_ctx数组的数据元素赋值
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}

m = ngx_modules[i]->ctx;  

if (m->create_conf) {  
    (*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle);  
    if ((*ctx)[ngx_modules[i]->ctx_index] == NULL) {  
        return NGX_CONF_ERROR;  
    }  
}  

}
//这里*ctx相当于是那个数组,通过ctx_index找到对应得存放event模块的配置结构的数据元素,比如Ngx_event_core_module通过create_conf创造出来的Ngx_event_core_conf_t,把它的地址存入到*ctx对应得数组中
* 为解析events块准备,设置要解析的模块以及指令的类型。
* 备份cf,解析完events块后恢复。
*/
pcf = *cf;
cf->ctx = ctx;
cf->module_type = NGX_EVENT_MODULE;
cf->cmd_type = NGX_EVENT_CONF;

/**
* 解析events块
*/
rv = ngx_conf_parse(cf, NULL);
//开始解析event模块,也就是解析event模块里面的指令,
接下来接着调用init_conf回调
这里会对对应得配置结构体里面的内容做一些默认的赋值

2.注意每一个模块的接口中有一些init_module和init_process函数,它们是在解析完配置文件的时候进行调用,在ngx_event_core_module中有俩个接口,一个是ngx_event_module_init,(它用来初始化共享内存中的数据比如accept锁),还有一个是worker进程创建以后被调用,该模块负责事件模块的加载
1》ngx_events_module_init:
他是在ngx_cycle_t的函数中被调用
主要是获取time_resolution以及获取最大连接数的限制
同时创建共享内存,同时将accept锁和连接计数器同时放入该内存中
2》ngx_event_process_init
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
//获取响应模块的配置结构体
//master进程打开,worker进程大于1,已经创建了accept_mutex
//才打开accept互斥体
if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
ngx_use_accept_mutex = 1; //使用互斥体
ngx_accept_mutex_held = 0; //是否获得accept互斥体
ngx_accept_mutex_delay = ecf->accept_mutex_delay;//争抢互斥体失败后,等待下次争抢时间间隔

} else {  
    ngx_use_accept_mutex = 0;  
}  

//初始化计数器,此处将会创建一颗红黑树,来维护计时器
if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
return NGX_ERROR;
}

for (m = 0; ngx_modules[m]; m++) {
//这里之前讲过,跳过非NGX_EVENT_MODULE模块
if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
continue;
}
//非use配置指令指定的模块跳过,linux默认epoll
if (ngx_modules[m]->ctx_index != ecf->use) {
continue;
}

    module = ngx_modules[m]->ctx;  
    /*调用具体时间模块的init函数 

    由于nginx实现了很多事件模块,比如:epoll、poll、select、dqueue、aio 
    (这些模块位于src/event/modules目录中),所以nginx对事件模块进行了一层抽象, 
    方便了不同的系统使用不同的事件模型,也便于扩展新的时间模型,我们的重点应该 
    放在epoll上。 

    此处的init回调,其实就是调用了ngx_epoll_init函数。module->actions结构封装了 
    epoll的所有接口函数。nginx就是通过actions结构将epoll注册到事件抽象层中。 
    actions的类型是ngx_event_action_t,位于src/event/ngx_event.h 
    */  
    if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {  
        /* fatal */  
        exit(2);  
    }  

    break;  
}  
 //创建全局的ngx_connection_t数组,保存所有的connection  
//由于这个过程是在各个worker进程中执行的,所以每个worker都有自己的connection数组  
cycle->connections =  
    ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);  
if (cycle->connections == NULL) {  
    return NGX_ERROR;  
}  

c = cycle->connections;  
//接下来的代码就是创建读事件组,创建写事件组,以及初始化整个connection数组,同时对cycle中的成员进行一些赋值

//接下来的代码是为每一个监听套接字从connection数组中分配一个连接,同时去初始化那个连接中的一些属性

//注册监听套接口毒事件的回调函数 ngx_event_accept
//使用了accept_mutex,暂时不将监听套接字放入epoll中,而是
//等到worker抢到accept互斥体后,再放入epoll,避免惊群的发生
rev->handler = ngx_event_accept;
//如果使用了accept_mutex,暂时不降套接字放入epoll中,而是等到worker进程抢到互斥体以后才放入epoll,避免惊群的发生
if (ngx_use_accept_mutex) {
continue;
}

      //该标志的意思是采用实时信号
    if (ngx_event_flags & NGX_USE_RTSIG_EVENT) { // 
        if (ngx_add_conn(c) == NGX_ERROR) {  
            return NGX_ERROR;  
        }  

    } else {  
        //没有使用accept互斥体,那么就将此监听套接字放入epoll中。  
        if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {  
            return NGX_ERROR;  
        }  
    }  

上面这部分代码也就完成了epoll_ctl的功能
3》ngx_epoll_init
在上面代码中调用的actions.init对于epoll模块来说其实就是ngx_epoll_init,他就是ngx_epoll_module模块,通过上述的use指令确定使用哪一个模块,然后再调用对应得Init函数,也即是epoll_create的实现过程:
ep = epoll_create(cycle->connection_n / 2);
ngx_event_actions = ngx_epoll_module_ctx.actions;
//为抽象事件赋值模型,该ngx_event_actions就是ngx_event_core_module中的
4》最后的就是处理事件的函数了,即epoll_wait被调用的地方
ngx_process_events_and_timers
/*
ngx_use_accept_mutex变量代表是否使用accept互斥体
默认是使用,可以通过accept_mutex off;指令关闭;
accept mutex 的作用就是避免惊群,同时实现负载均衡
*/
if (ngx_use_accept_mutex) {
ngx_accept_disabled变量在ngx_event_accept函数中计算。
如果ngx_accept_disabled大于0,就表示该进程接受的链接过多,
因此放弃一次争抢accept mutex的机会,同时将自己减一。
然后,继续处理已有连接上的事件。
nginx就利用这一点实现了继承关于连接的基本负载均衡。
*/
if (ngx_accept_disabled > 0) {
ngx_accept_disabled–;

    } else {  
       尝试锁accept mutex,只有成功获取锁的进程,才会将listen套接字放到epoll中。 
        因此,这就保证了只有一个进程拥有监听套接口,故所有进程阻塞在epoll_wait时, 
        才不会惊群现象。 
        if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {  
            return;  
        }  

if (ngx_accept_mutex_held) {
/*
如果进程获得了锁,将添加一个 NGX_POST_EVENTS 标志。
这个标志的作用是将所有产生的事件放入一个队列中,等释放后,在慢慢来处理事件。
因为,处理时间可能会很耗时,如果不先施放锁再处理的话,该进程就长时间霸占了锁,
导致其他进程无法获取锁,这样accept的效率就低了。
*/
flags |= NGX_POST_EVENTS;

        } else {  
            /* 
            没有获得所得进程,当然不需要NGX_POST_EVENTS标志。 
            但需要设置延时多长时间,再去争抢锁。 
            */  
            if (timer == NGX_TIMER_INFINITE  
                || timer > ngx_accept_mutex_delay)  
            {  
                timer = ngx_accept_mutex_delay;  
            }  
        }  
    }  

(void) ngx_process_events(cycle, timer, flags);
//该函数用于wait事件,具体到epoll中就是ngx_epoll_process_events函数
ngx_posted_accept_events是一个事件队列,暂存epoll从监听套接口wait到的accept事件。
前文提到的NGX_POST_EVENTS标志被使用后,会将所有的accept事件暂存到这个队列
*/
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);
} 该函数避免长期使用锁
ngx_posted_events代表着普通事件
if (ngx_posted_events) {
if (ngx_threaded) {
ngx_wakeup_worker_thread(cycle);

    } else {  
        ngx_event_process_posted(cycle, &ngx_posted_events);  
    }  
}  

}
5》在上文中还提到一个ngx_epoll_process_events
该函数的作用就是实现epoll_wait,并根据flags判断是否需要更新时间
//遍历本次epoll_wait返回的所有事件
for (i = 0; i < events; i++) {
//获取连接ngx_connection_t的地址
c = event_list[i].data.ptr;

    //连接的地址最后一位具有特殊意义:用于存储instance变量,将其取出来  
    instance = (uintptr_t) c & 1;  
    //无论是32位还是64位机器,其地址最后一位一定是0,获取真正地址  
    c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);  
    //取出读事件  
    rev = c->read;  

//事件需要延后处理
if (flags & NGX_POST_EVENTS) {
/*如果要在post队列中延后处理该事件,首先要判断它是新连接时间还是普通事件
以确定是把它加入到ngx_posted_accept_events队列或者ngx_posted_events队列中。*/
queue = (ngx_event_t **) (rev->accept ?
&ngx_posted_accept_events : &ngx_posted_events);
//将该事件添加到相应的延后队列中
ngx_locked_post_event(rev, queue);

        } else {  //在没有锁的情况则直接调用回调函数处理就可以
            //立即调用事件回调方法来处理这个事件  
            rev->handler(rev);  
        }  
    }  
 6》ngx_events_accept:实现连接的建立

监听到的新的连接实际上就是监听socket上的读事件,这个时候可以调用accept去建立连接,在nginx中每个socket都会被封装成一个连接结构,就是ngx_connection_t,每个ngx_connection_t中有读事件read和写事件write,它们都是ngx_event_t结构的,同时它内部还有一个handler回调指针,在发生读写事件的时候被调用,
ngx_event_accept它完成的主要任务就是在监听到连接的时候会调用,用于初始化连接,并且将事件再次添加到epoll中
函数的实现:
lc = ev->data; //事件的data属性代表着连接
ls = lc->listening;连接的listening属性代表着那个套接口
事件的available属性代表着有事件发生,所以这里有一个while循坏的迭代,用于确定所有已经发生的事件
在该while循坏中,首先通过accept获取一个新的连接s,而后计算ngx_accept_disabled,用它来做负载均衡,给s分配一个新的ngx_connection_t,接下来的额工作就是初始化该连接,并且设置该套接口的一些属性(比如会把它设置为非阻塞模式),
初始化好连接以后开始初始化它的事件部分,给事件中的一个ready属性设置,表示该事件可以被触发了,
if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
if (ngx_add_conn(c) == NGX_ERROR) {
ngx_close_accepted_connection(c);
return;
}
}
//最后会调用ngx_add_conn将新建的连接加入nginx的事件循坏中,在使用epoll时实际上会调用ngx_epoll_add_connection添加事件。
在最后会调用ls->handler(c),ls是监听socket,他就是在监听套接口accept到新的连接的时候被调用,即ngx_http_init_connection用来完成连接的初始化,该handler是在ngx_http_add_listening中被设置的,
ngx_http_init_connection:主要设置读事件的handler
最核心的代码:
rev = c->read;
rev->handler = ngx_http_init_request;
c->write->handler = ngx_http_empty_handler
设置读事件的handler位ngx_http_init_request
ps:这个时候连接已经建立,开始监听该连接上的事件了,所以当在连接上有数据的时候就代表有事件发生了,也就意味着有客户端的请求过来了,因此它用于初始化客户端的请求,
然后在把该事件添加到定时器和事件循坏中,但是因为上面已调用ngx_add_coon把事件添加进去了,所以这里的添加事件到循坏事件其实是什么都不做的

接下来当请求发生的时候就会调用ngx_http_init_request来处理请求了详细见请求处理:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值