Nginx启动以及IOCP模型.docx

上传人:b****6 文档编号:4687216 上传时间:2022-12-07 格式:DOCX 页数:38 大小:30.78KB
下载 相关 举报
Nginx启动以及IOCP模型.docx_第1页
第1页 / 共38页
Nginx启动以及IOCP模型.docx_第2页
第2页 / 共38页
Nginx启动以及IOCP模型.docx_第3页
第3页 / 共38页
Nginx启动以及IOCP模型.docx_第4页
第4页 / 共38页
Nginx启动以及IOCP模型.docx_第5页
第5页 / 共38页
点击查看更多>>
下载资源
资源描述

Nginx启动以及IOCP模型.docx

《Nginx启动以及IOCP模型.docx》由会员分享,可在线阅读,更多相关《Nginx启动以及IOCP模型.docx(38页珍藏版)》请在冰豆网上搜索。

Nginx启动以及IOCP模型.docx

Nginx启动以及IOCP模型

Nginx启动以及IOCP模型

本文档针对Nginx1.11.7版本,分析Windows下的相关代码,虽然服务器可能用Linux更多,但是windows平台下的代码也基本相似

,另外windows的IOCP完成端口,异步IO模型非常优秀,很值得一看。

Nginx启动

曾经有朋友问我,面对一个大项目的源代码,应该从何读起呢?

我给他举了一个例子,我们学校大一大二是在紫金港校区,到了

大三搬到玉泉校区,但是大一的时候也会有时候有事情要去玉泉办。

偶尔会去玉泉,但是玉泉校区不熟悉,于是跟着XX地图或者

跟着学长走。

因为是办事情,所以一般也就是局部走走,比如在学院办公楼里面走走。

等到大三刚来到玉泉,会发现,即使是自己

以前来过几次,也觉得这个校区完全陌生,甚至以前来过的地方,也显得格外生疏。

但是当我们真正在玉泉校区开始学习生活了,

每天从寝室走到教室大多就是一条路,教超就是另一条路,这两条主要的路走几遍之后,有时候顺路去旁边的小路看看,于是慢慢

也熟悉了这个新的校区。

源代码的阅读又何尝不是这样呢,如果没有一条主要的路线,总是局部看看,浅尝辄止不说,还不容易把握整体的结构。

各模块之间

的依赖也容易理不清。

如果有一条比较主干的线路,去读源代码,整体结构和思路也会变得明晰起来。

当然我也是持这种看法:

博客、

文章的作者,写文章的思路作者自己是清楚的,读者却不一定能看得到;而且大家写东西都难免会有疏漏。

看别人写的源码分析指引

等等,用一种比较极端的话来说,是一种自我满足,觉得自己很快学到了很多源码级别的知识,但是其实想想,学习乎,更重要的是

学习能力的锻炼,通过源码的学习,学习过程中自己结合自己情况的思考,甚至结合社会哲学的思考,以及读源码之后带来的收益,

自己在平时使用框架、库的时候,出了问题的解决思路,翻阅别人源码来找到bug的能力。

如果只是单单看别人写的源码分析,与写

代码的时候只去抄抄现成的代码,某种程度上是有一定相似性的。

我自己是使用Go为主的,之前对于一流的nginx中间件也没有太多了解,也是第一次去看,水平不足之处,还望海涵。

回归正题,

Nginx的源代码分析,也是要找一条主要的路线,对于很多程序来说,启动过程就是一条很不错的路线,找找nginx的入口函数main

,发现在/src/core/nginx.c中,代码大概如下:

intngx_cdeclmain(intargc,char*const*argv){

...//先是一些变量声明

ngx_debug_init();

...

ngx_pid=ngx_getpid();

...

init_cycle.pool=ngx_create_pool(1024,log);

...

cycle=ngx_init_cycle(&init_cycle);

...

if(ngx_signal){

returnngx_signal_process(cycle,ngx_signal);

}

...

if(ngx_create_pidfile(&ccf->pid,cycle->log)!

=NGX_OK){

return1;

}

...

if(ngx_process==NGX_PROCESS_SINGLE){

ngx_single_process_cycle(cycle);

}else{

ngx_master_process_cycle(cycle);

}

return0

这段代码大致看上去,先是做了一些初始化的事情,包括pool看起来应该是内存池之类的变量的分配,获取系统信息,

初始化日志系统等等,因为还没有进入相应函数去仔细看,所以先放着。

用过nginx的同学应该了解,nginx命令行

运行./nginx后,他直接就运行服务了,很静默,然后即使用Ctrl+C也关不掉。

但是再开一个console,运行

./nginx-h就会看到:

nginxversion:

nginx/1.11.7

Usage:

nginx[-?

hvVtTq][-ssignal][-cfilename][-pprefix][-gdirectives]

Options:

-?

-h:

thishelp

-v:

showversionandexit

-V:

showversionandconfigureoptionsthenexit

-t:

testconfigurationandexit

-T:

testconfiguration,dumpitandexit

-q:

suppressnon-errormessagesduringconfigurationtesting

-ssignal:

sendsignaltoamasterprocess:

stop,quit,reopen,reload

-pprefix:

setprefixpath(default:

NONE)

-cfilename:

setconfigurationfile(default:

conf/nginx.conf)

-gdirectives:

setglobaldirectivesoutofconfigurationfile

这是nginx的命令行参数介绍,要退出nginx需要用nginx-sstop给已经打开的nginx进程发送信号,让其退出。

而且nginx还支持平滑的重启,这种重启在更改nginx配置时非常有用,重启服务器的过程,实际上是nginx自己内部

的一种处理,重新载入新的配置,但是却不影响已经有的一些连接,所以称之为平滑重启。

gracefullystopnginx…

而main函数在初始化之后,做的就是命令行参数的解析,如果是显示版本,那么显示一个版本信息,就退出;如果是设置

配置文件,那么去调用设置配置文件的相应处理;如果是发送控制信号,那么returnngx_signal_process(cycle,ngx_signal);

处理信号等等。

这里还有个小trick,就是关于pid文件,程序把自己的pid写入一个文件,然后就可以防止启动多个进程,

这是一个比较常用的小技巧。

关于ngx_single_process_cycle(cycle)这应该是单进程的情况,一般而言现在的服务器

都是多核为主,所以我们去ngx_master_process_cycle(cycle)Master进程的主函数看一看。

主进程

ngx_master_process_cycle函数在/src/os/win32/ngx_process_cycle.c中,该函数接受一个参数,这个参数比较

复杂,但是可以看出,应该是和每次nginx循环的生命周期有关,这里认为nginx每平滑重启一次,就是一次循环。

代码

分为几个部分来看:

voidngx_master_process_cycle(ngx_cycle_t*cycle){

...

if(ngx_process==NGX_PROCESS_WORKER){

//ngx_process标识进程的身份,如果本进程应该是工作者进程,就去执行工作者应该做的

ngx_worker_process_cycle(cycle,ngx_master_process_event_name);

return;

}

...

SetEnvironmentVariable("ngx_unique",ngx_unique);//设置环境变量,表示nginx主进程已经运行

...

ngx_master_process_event=CreateEvent(NULL,1,0,ngx_master_process_event_name);

if(ngx_master_process_event==NULL){

ngx_log_error(NGX_LOG_ALERT,cycle->log,ngx_errno,

"CreateEvent(\"%s\")failed",

ngx_master_process_event_name);

exit

(2);

}

if(ngx_create_signal_events(cycle)!

=NGX_OK){

exit

(2);

}

ngx_sprintf((u_char*)ngx_cache_manager_mutex_name,

"ngx_cache_manager_mutex_%s%Z",ngx_unique);

ngx_cache_manager_mutex=CreateMutex(NULL,0,

ngx_cache_manager_mutex_name);

if(ngx_cache_manager_mutex==NULL){

ngx_log_error(NGX_LOG_ALERT,cycle->log,ngx_errno,

"CreateMutex(\"%s\")failed",ngx_cache_manager_mutex_name);

exit

(2);

}

events[0]=ngx_stop_event;

events[1]=ngx_quit_event;

events[2]=ngx_reopen_event;

events[3]=ngx_reload_event;

ngx_close_listening_sockets(cycle);

if(ngx_start_worker_processes(cycle,NGX_PROCESS_RESPAWN)==0){

exit

(2);

}

...

}

理解这段代码,需要了解Windows系统的一点点事件相关API,CreateEvent可以创建一个事件,之后可以通过一些方法

比如SetEvent可以使得这个事件被激活,进程或者线程也可以通过WaitForSingleObejct等API去等待一个事件的发

生。

这段代码就是创建了一些事件,包括stop,quit,reopen和reload,这些事件是在ngx_create_signal_events

函数中创建的:

staticngx_int_t

ngx_create_signal_events(ngx_cycle_t*cycle)

{

ngx_sprintf((u_char*)ngx_stop_event_name,

"Global\\ngx_stop_%s%Z",ngx_unique);

ngx_stop_event=CreateEvent(NULL,1,0,ngx_stop_event_name);

if(ngx_stop_event==NULL){

ngx_log_error(NGX_LOG_ALERT,cycle->log,ngx_errno,

"CreateEvent(\"%s\")failed",ngx_stop_event_name);

returnNGX_ERROR;

}

 

ngx_sprintf((u_char*)ngx_quit_event_name,

"Global\\ngx_quit_%s%Z",ngx_unique);

...

之后,主进程调用ngx_close_listening_sockets(cycle)关闭正在侦听的套接字,这样之后的连接就不会进来了,

因为主进程循环肯定是重启或者初始化的时候被调用的。

之后调用ngx_start_worker_processes函数去启动工作者

线程。

我们看看ngx_start_worker_process函数,同样在这个文件里:

staticngx_int_t

ngx_start_worker_processes(ngx_cycle_t*cycle,ngx_int_ttype)

{

ngx_int_tn;

ngx_core_conf_t*ccf;

ngx_log_error(NGX_LOG_NOTICE,cycle->log,0,"startworkerprocesses");

ccf=(ngx_core_conf_t*)ngx_get_conf(cycle->conf_ctx,ngx_core_module);

for(n=0;nworker_processes;n++){

if(ngx_spawn_process(cycle,"worker",type)==NGX_INVALID_PID){

break;

}

}

returnn;

}

这个函数先是读取了本次循环的配置,根据配置中的worker_process的设置来启动相应数量的工作者进程,配置文件

在/conf/nginx.conf中:

#usernobody;

worker_processes8;

#error_loglogs/error.log;

#error_loglogs/error.lognotice;

#error_loglogs/error.loginfo;

#pidlogs/nginx.pid;

 

events{

worker_connections65536;

}

...

当然如果配置文件中没有设置,以及新创建的配置文件中如何设置默认值,这些都在/src/core/nginx.c中,但是不是

非常重要,所以暂时略过。

回归ngx_master_process_cycle函数,该函数在创建了事件之后,会进入一个死循环:

for(;;){

nev=4;

for(n=0;n

if(ngx_processes[n].handle){

events[nev++]=ngx_processes[n].handle;

}

}

if(timer){

timeout=timer>ngx_current_msec?

timer-ngx_current_msec:

0;

}

ev=WaitForMultipleObjects(nev,events,0,timeout);

err=ngx_errno;

ngx_time_update();

ngx_log_debug1(NGX_LOG_DEBUG_CORE,cycle->log,0,

"masterWaitForMultipleObjects:

%ul",ev);

if(ev==WAIT_OBJECT_0){

ngx_log_error(NGX_LOG_NOTICE,cycle->log,0,"exiting");

if(ResetEvent(ngx_stop_event)==0){

ngx_log_error(NGX_LOG_ALERT,cycle->log,0,

"ResetEvent(\"%s\")failed",ngx_stop_event_name);

}

if(timer==0){

timer=ngx_current_msec+5000;

}

ngx_terminate=1;

ngx_quit_worker_processes(cycle,0);

continue;

}

if(ev==WAIT_OBJECT_0+1){

ngx_log_error(NGX_LOG_NOTICE,cycle->log,0,"shuttingdown");

if(ResetEvent(ngx_quit_event)==0){

ngx_log_error(NGX_LOG_ALERT,cycle->log,0,

"ResetEvent(\"%s\")failed",ngx_quit_event_name);

}

ngx_quit=1;

ngx_quit_worker_processes(cycle,0);

continue;

}

...

if(ev>WAIT_OBJECT_0+3&&ev

ngx_log_debug0(NGX_LOG_DEBUG_CORE,cycle->log,0,"reapworker");

live=ngx_reap_worker(cycle,events[ev]);

if(!

live&&(ngx_terminate||ngx_quit)){

ngx_master_process_exit(cycle);

}

continue;

}

if(ev==WAIT_TIMEOUT){

ngx_terminate_worker_processes(cycle);

ngx_master_process_exit(cycle);

}

if(ev==WAIT_FAILED){

ngx_log_error(NGX_LOG_ALERT,cycle->log,err,

"WaitForMultipleObjects()failed");

continue;

}

ngx_log_error(NGX_LOG_ALERT,cycle->log,0,

WaitForMultipleObjects()returnedunexpectedvalue%ul",ev);

}

首先介绍WaitForMultipleObjects,这个函数会等待多个内核对象,可以是事件,也可以是锁,进程等等。

这个循环中,每次

循环先添加了ngx_last_process个进程到了事件数组中,这个ngx_processes大概是上次循环中使用的进程组。

如果定义的

stop,quit,reload,reopen四种事件触发,分别调用相关函数去关闭或者重启工作者进程。

如果是上次循环中使用的进

程死亡,那么就去重启这个进程,调用ngx_reap_worker函数,这个函数在确认旧的进程已经死亡后,会调用ngx_spawn_process

去重启一个新的进程。

ngx_spawn_process会调用ngx_execute去开一个新的进程,这部分的细节,放入下一节再讲。

这样我们

了解了主进程在启动后,会进入事件处理循环来处理nginx-s发送的指令以及处理进程组死亡的重启。

那么我们看看工作者进程

是做什么的。

工作者进程

我们了解到,主进程调用ngx_start_worker_process函数根据配置文件启动多个工作者进程,这个函数中调用了ngx_spawn_process

来启动新的工作者进程,那么我们来看看ngx_spawn_process是如何启动一个新的进程。

以下是部分代码(位于/src/os/win32/ngx_process.c):

ngx_pid_tngx_spawn_process(ngx_cycle_t*cycle,char*name,ngx_int_trespawn){

...//变量定义

//第一次主循环传入的是NGX_PROCESS_JUST_RESPAWN==-3

if(respawn>=0){

s=respawn;

}else{

for(s=0;s

if(ngx_processes[s].handle==NULL){

break;

}

}

if(s==NGX_MAX_PROCESSES){

ngx_log_error(NGX_LOG_ALERT,cycle->log,0,

"nomorethan%dprocessescanbespawned",

NGX_MAX_PROCESSES);

returnNGX_INVALID_PID;

}

}

//得到Nginx的文件路径

n=GetModuleFileName(NULL,file,MAX_PATH);

if(n==0){

ngx_log_error(NGX_LOG_ALERT,cycle->log,ngx_errno,

"GetModuleFileName()failed");

returnNGX_INVALID_PID;

}

file[n]='\0';

...

ctx.path=file;

...

pid=ngx_execute(cycle,&ctx);//创建新进程

...

这部分是先找到ngx_process中的索引,然后放入一个新的进程,那么我们看看ngx_execute函数是怎么执行的:

ngx_pid_tngx_execute(ngx_cycle_t*cycle,ngx_exec_ctx_t*ctx)

{

...

if(CreateProcess(ctx->path,ctx->args,

NULL,NULL,0,CREATE_NO_WINDOW,NULL,NULL,&si,&pi)

==0)

{

ngx_log_error(NGX_LOG_CRIT,cycle->log,ngx_errno,

"CreateProcess(\"%s\")failed",ngx_argv[0]);

return0;

}

ctx->child=pi.hProcess;

if(CloseHandle(pi.hThread)==0){

ngx_log_error(NGX_LOG_ALERT,cycle->log,ngx_errno,

"CloseHandle(pi.hThread)failed");

}

ngx_log_error(NGX_LOG_NOTICE,cycle->log,0,

"start%sprocess%P",ctx->name,pi.dwProcessId);

returnpi.dwProcessId;

}

这个函数通过调用ngx_execute开启新的进程,并把进程句柄存入了context中,返回pid。

创建系统进程之后,会调用

WaitForMultipleObjects等待两个事件,一个是ngx_master_process_event,这个事件在主进程循环中定义,另一

个是新开的进程死亡。

如果主进程事件触发,那么会使用OpenEvent设置新进程的事件为以前创建的事件。

但是可能是因为

我手头的版本还在开发中,我没有在代码里面找到

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高中教育 > 理化生

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1