1. 程式人生 > 程式設計 >nginx網路通訊模組設計與實現分析

nginx網路通訊模組設計與實現分析

nginx作為後端程式不可缺少的中介軟體,憑藉其優異的效能和穩定,獲得了大量的使用者

目錄結構

nginx的檔案目錄非常清晰,雖然有6個目錄,但是可以歸成兩類,core目錄存放nginx生命週期相關的函式,比如master、worker程式的建立,模組建構函式呼叫,核心資料結構和演演算法實現

其它目錄主要就是存放nginx功能的擴充套件了、nginx幾乎所有的功能都是通過擴充套件實現的

入口程式

src/core/nginx.c::main()

在入口程式中,nginx主要是處理了命令列引數,如果沒有帶引數執行nginx,則啟動nginx伺服器ngx_master_process_cycle(cycle

核心呼叫如下

  1. ngx_strerror_init() 初始化錯誤碼存放連結串列
  2. ngx_get_options(argc,argv) 將命令列引數解析到全域性變數
  3. 初始化日誌系統: ngx_log_init(ngx_prefix)
  4. ngx_save_argv(&init_cycle,argc,argv) 儲存所有命令列引數到全域性變數
  5. ngx_process_options(&init_cycle)

給啟用的nginx模組進行編號

ngx_max_module = 0;
for (i = 0; ngx_modules[i]; i++) {
    ngx_modules[i]->index = ngx_max_module++;
}
複製程式碼
  1. ngx_init_cycle(&init_cycle) 初始化cycle全域性變數
  2. ngx_signal_process(cycle,ngx_signal) 對nginx master傳送訊號kill()
ngx_int_t
ngx_os_signal_process(ngx_cycle_t *cycle,char *name,ngx_int_t pid)
{
    ngx_signal_t  *sig;

    for (sig = signals; sig->signo != 0; sig++) {
        if (ngx_strcmp(name,sig->name) == 0
) { if (kill(pid,sig->signo) != -1) { return 0; } ngx_log_error(NGX_LOG_ALERT,cycle->log,ngx_errno,"kill(%P,%d) failed",pid,sig->signo); } } return 1; } 複製程式碼
  1. 建立pidfile ngx_create_pidfile(&ccf->pid,cycle->log)
  2. 啟動nginx master程式 ngx_master_process_cycle(cycle)

模組/鉤子機制

nginx的模組是通過在nginx執行的各個生命週期呼叫模組的鉤子函式實現的 拿src/event/ngx_event.c:184舉例

ngx_module_t  ngx_event_core_module = {
    NGX_MODULE_V1,&ngx_event_core_module_ctx,/* module context */
    ngx_event_core_commands,/* module directives */
    NGX_EVENT_MODULE,/* module type */
    NULL,/* init master */
    ngx_event_module_init,/* init module */
    ngx_event_process_init,/* init process */
    NULL,/* init thread */
    NULL,/* exit thread */
    NULL,/* exit process */
    NULL,/* exit master */
    NGX_MODULE_V1_PADDING
};
複製程式碼

可以看到nginx為每個模組都支援了7個鉤子函式

回撥函式 作用
init master master程式建立時呼叫
init module 初始化模組時呼叫
init process worker程式初始化時呼叫
init thread 未使用
exit thread 未使用
exit process worker程式退出時呼叫
exit master master程式退出時呼叫

事件驅動選擇

nginx的事件驅動在src/event/ngx_event.c::ngx_event_core_init_conf(ngx_cycle_t *cycle,void *conf)函式中選擇,核心程式碼如下

static char *
ngx_event_core_init_conf(ngx_cycle_t *cycle,void *conf)
{
    ngx_event_conf_t  *ecf = conf;
    ...
#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)

    fd = epoll_create(100);

    if (fd != -1) {
        close(fd);
        module = &ngx_epoll_module;

    } else if (ngx_errno != NGX_ENOSYS) {
        module = &ngx_epoll_module;
    }

#endif
    ...
    ngx_conf_init_uint_value(ecf->use,module->ctx_index);
    ...
}
複製程式碼

可以看到nginx預設會優先選擇epoll網路事件模型進行驅動,往後才是kqueue、select,然後把最後確定使用的網路驅動模組的索引存到ecf->use這個指標中,後面就可以通過這個use指標呼叫nginx使用的網路庫了

事件初始化函式呼叫

在worker程式初始化時:src/event/ngx_event.c:ngx_event_process_init()

for (m = 0; ngx_modules[m]; m++) {
    if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
        continue;
    }

    if (ngx_modules[m]->ctx_index != ecf->use) {
        continue;
    }

    module = ngx_modules[m]->ctx;

    if (module->actions.init(cycle,ngx_timer_resolution) != NGX_OK) {
        /* fatal */
        exit(2);
    }

    break;
}
複製程式碼

遍歷所有模組,找出目前系統選擇的網路庫ngx_modules[m]->ctx_index != ecf->use,然後呼叫初始化函式進行初始化module->actions.init(cycle,ngx_timer_resolution)

actions結構體在src/event/ngx_event.h:ngx_event_actions_t

typedef struct {
    ngx_int_t  (*add)(ngx_event_t *ev,ngx_int_t event,ngx_uint_t flags);
    ngx_int_t  (*del)(ngx_event_t *ev,ngx_uint_t flags);

    ngx_int_t  (*enable)(ngx_event_t *ev,ngx_uint_t flags);
    ngx_int_t  (*disable)(ngx_event_t *ev,ngx_uint_t flags);

    ngx_int_t  (*add_conn)(ngx_connection_t *c);
    ngx_int_t  (*del_conn)(ngx_connection_t *c,ngx_uint_t flags);

    ngx_int_t  (*process_changes)(ngx_cycle_t *cycle,ngx_uint_t nowait);
    ngx_int_t  (*process_events)(ngx_cycle_t *cycle,ngx_msec_t timer,ngx_uint_t flags);

    ngx_int_t  (*init)(ngx_cycle_t *cycle,ngx_msec_t timer);
    void       (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
複製程式碼

初始化在src/event/modules/ngx_epoll_module.c:ngx_event_module_t ngx_epoll_module_ctx

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 */
        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 */
    }
};
複製程式碼

init函式指標對應呼叫的就是ngx_epoll_init函式了

static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle,ngx_msec_t timer)
    ...
    ep = epoll_create(cycle->connection_n / 2);
    ...
}
複製程式碼

可以看到主要是呼叫epoll_create函式了,其餘的網路庫呼叫可以通過ngx_event_actions_t、ngx_epoll_module_ctx找到了

一些注意的點

本文基於nginx 1.2.0 寫成,原始碼下載參考:nginx.org/download/

參考資料

  1. Inside NGINX: How We Designed for Performance & Scale
  2. github.com/Toudsour/ng…