14 張圖搞懂 Nginx 高性能網(wǎng)絡(luò)工作原理!
在單進(jìn)程的網(wǎng)絡(luò)編程模型中。所有的網(wǎng)絡(luò)相關(guān)的動(dòng)作都是在一個(gè)進(jìn)程里完成的,如監(jiān)聽 socket 的創(chuàng)建, bind、listen。再比如 epoll 的創(chuàng)建、要監(jiān)聽事件的添加,以及 epoll_wait 等待時(shí)間發(fā)生。這些統(tǒng)統(tǒng)都是在一個(gè)進(jìn)程里搞定。
一個(gè)客戶端和使用了 epoll 的服務(wù)端的交互過程如下圖所示。

以下是其大概的代碼示例(沒耐心看的同學(xué)可以先)。
int?main(){
?//監(jiān)聽
?lfd?=?socket(AF_INET,SOCK_STREAM,0);
?bind(lfd,?...)
?listen(lfd,?...);
?//創(chuàng)建epoll對(duì)象,并把?listen?socket的事件管理起來(lái)
?efd?=?epoll_create(...);
?epoll_ctl(efd,?EPOLL_CTL_ADD,?lfd,?...);
?//事件循環(huán)
?for?(;;)
?{
??size_t?nready?=?epoll_wait(efd,?ep,?...);
??for?(int?i?=?0;?i?
???if(ep[i].data.fd?==?lfd){
????//lfd上發(fā)生事件表示都連接到達(dá),accept接收它
????fd?=?accept(listenfd,?...);
????epoll_ctl(efd,?EPOLL_CTL_ADD,?fd,?...);
???}else{
????//其它socket發(fā)生的事件都是讀寫請(qǐng)求、或者關(guān)閉連接
????...
???}
??}
?}
}
在單進(jìn)程模型中,不管有多少的連接,是幾萬(wàn)還是幾十萬(wàn),服務(wù)器都是通過 epoll 來(lái)監(jiān)控這些連接 socket 上的可讀和可寫事件。當(dāng)某個(gè) socket 上有數(shù)據(jù)發(fā)生的時(shí)候,再以非阻塞的方式對(duì) socket 進(jìn)行讀寫操作。
事實(shí)上,Redis 5.0 及以前的版本中,它的網(wǎng)絡(luò)部分去掉對(duì) handler 的封裝,去掉時(shí)間事件以后,代碼基本和上述 demo 非常接近。而且因?yàn)?Redis 的業(yè)務(wù)特點(diǎn)只需要內(nèi)存 IO,且 CPU 計(jì)算少,所以可以達(dá)到數(shù)萬(wàn)的 QPS。

但是單進(jìn)程的問題也是顯而易見的,沒有辦法充分發(fā)揮多核的優(yōu)勢(shì)。所以目前業(yè)界絕大部分的后端服務(wù)還都是需要基于多進(jìn)程的方式來(lái)進(jìn)行開發(fā)的。到了多進(jìn)程的時(shí)候,更復(fù)雜的問題多進(jìn)程之間的配合和協(xié)作問題就產(chǎn)生了。比如
哪個(gè)進(jìn)程執(zhí)行監(jiān)聽 listen ,以及 accept 接收新連接?
哪個(gè)進(jìn)程負(fù)責(zé)發(fā)現(xiàn)用戶連接上的讀寫事件?
當(dāng)有用戶請(qǐng)求到達(dá)的時(shí)候,如何均勻地將請(qǐng)求分散到不同的進(jìn)程中?
需不需要單獨(dú)搞一部分進(jìn)程執(zhí)行計(jì)算工作
…
事實(shí)上,以上這些問題并沒有標(biāo)準(zhǔn)答案。各大應(yīng)用或者網(wǎng)絡(luò)框架都有自己不同的實(shí)現(xiàn)方式。為此業(yè)界還專門總結(jié)出了兩類網(wǎng)絡(luò)設(shè)計(jì)模式 - Reactor 和 Proactor。不過今天我不想討論這種抽象模式,而是想帶大家看一個(gè)具體的 Case - Nginx 是如何在多進(jìn)程下使用 epoll 的。
一、 Nginx Master 進(jìn)程初始化
在 Nginx 中,將進(jìn)程分成了兩類。一類是 Master 進(jìn)程,一類是 Worker 進(jìn)程。
在 Master 進(jìn)程中,主要的任務(wù)是負(fù)責(zé)啟動(dòng)整個(gè)程序、讀取配置文件、監(jiān)聽和處理各種信號(hào),并對(duì) Worker 進(jìn)程進(jìn)行統(tǒng)籌管理。
不過今天我們要查看的重點(diǎn)問題是看網(wǎng)絡(luò)。在 Master 進(jìn)程中,和網(wǎng)絡(luò)相關(guān)的操作非常簡(jiǎn)單就是創(chuàng)建了 socket 并對(duì)其進(jìn)行 bind 和 監(jiān)聽。

具體細(xì)節(jié)我們來(lái)看 Main 函數(shù)。
//file:?src/core/nginx.c
int?ngx_cdecl?main(int?argc,?char?*const?*argv)
{
?ngx_cycle_t??????*cycle,?init_cycle;
?//1.1?ngx_init_cycle?中開啟監(jiān)聽
?cycle?=?ngx_init_cycle(&init_cycle);
?//1.2?啟動(dòng)主進(jìn)程循環(huán)
?ngx_master_process_cycle(cycle);
}
在 Nginx 中,ngx_cycle_t 是非常核心的一個(gè)結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體存儲(chǔ)了很多東西,也貫穿了好多的函數(shù)。其中對(duì)端口的 bind 和 listen 就是在它執(zhí)行時(shí)完成的。
ngx_master_process_cycle 是 Master 進(jìn)程的主事件循環(huán)。它先是根據(jù)配置啟動(dòng)指定數(shù)量的 Worker 進(jìn)程,然后就開始關(guān)注和處理重啟、退出等信號(hào)。接下來(lái)我們分兩個(gè)小節(jié)來(lái)更詳細(xì)地看。
1.1 Nginx 的服務(wù)端口監(jiān)聽
我們看下 ngx_init_cycle 中是如何執(zhí)行 bind 和 listen 的。
//file:?src/core/ngx_cycle.c
ngx_cycle_t?*ngx_init_cycle(ngx_cycle_t?*old_cycle)
{
?......
?if?(ngx_open_listening_sockets(cycle)?!=?NGX_OK)?{
??goto?failed;
?}
}
真正的監(jiān)聽還是在 ngx_open_listening_sockets 函數(shù)中,繼續(xù)看它的源碼。
//file:?src/core/ngx_connection.c
ngx_int_t?ngx_open_listening_sockets(ngx_cycle_t?*cycle)
{
?......
?//要監(jiān)聽的?socket?對(duì)象
?ls?=?cycle->listening.elts;
?for?(i?=?0;?i?listening.nelts;?i++)?{
??//獲取第i個(gè)socket
??s?=?ngx_socket(ls[i].sockaddr->sa_family,?ls[i].type,?0);
??//綁定
??bind(s,?ls[i].sockaddr,?ls[i].socklen)
??//監(jiān)聽
??listen(s,?ls[i].backlog)
??ls[i].listen?=?1;
??ls[i].fd?=?s;
?}
}
在這個(gè)函數(shù)中,遍歷要監(jiān)聽的 socket。如果是啟用了 REUSEPORT 配置,那先把 socket 設(shè)置上 SO_REUSEPORT 選項(xiàng)。然后接下來(lái)就是大家都熟悉的 bind 和 listen。所以,bind 和 listen 是在 Master 進(jìn)程中完成的。
1.2 Master 進(jìn)程的主循環(huán)
在 ngx_master_process_cycle 中主要完成兩件事。
啟動(dòng) Worker 進(jìn)程
將 Master 進(jìn)程推入事件循環(huán)
在創(chuàng)建 Worker 進(jìn)程的時(shí)候,是通過 fork 系統(tǒng)調(diào)用讓 Worker 進(jìn)程完全復(fù)制自己的資源,包括 listen 狀態(tài)的 socket 句柄。

我們接下來(lái)看詳細(xì)的代碼。
//file:?src/os/unix/ngx_process_cycle.c
void?ngx_master_process_cycle(ngx_cycle_t?*cycle)
{
?......
?ngx_start_worker_processes(cycle,?ccf->worker_processes,
??????????NGX_PROCESS_RESPAWN);
?//進(jìn)入主循環(huán),等待接收各種信號(hào)
?for?(?;;?)?{
??//ngx_quit
??//ngx_reconfigure
??//ngx_restart
??...
?}
}
主進(jìn)程在配置中讀取到了 Worker 進(jìn)程的數(shù)量 ccf->worker_processes。通過 ngx_start_worker_processes 來(lái)啟動(dòng)指定數(shù)量的 Worker。
//file:src/os/unix/ngx_process_cycle.c
static?void?ngx_start_worker_processes(...)
{
?for?(i?=?0;?i???ngx_spawn_process(cycle,?ngx_worker_process_cycle,
????????(void?*)?(intptr_t)?i,?"worker?process",?type);
??...
?}
}
上述代碼中值得注意的是,在調(diào)用 ngx_spawn_process 時(shí)的幾個(gè)參數(shù)
cycle:nginx 的核心數(shù)據(jù)結(jié)構(gòu)
cngx_worker_process_cycle:worker 進(jìn)程的入口函數(shù)
ci: 當(dāng)前 worker 的序號(hào)
//file:?src/os/unix/ngx_process.c
ngx_pid_t?ngx_spawn_process(ngx_cycle_t?*cycle,?ngx_spawn_proc_pt?proc,...)
{
?pid?=?fork();
?switch?(pid)?{
??case?-1:?//出錯(cuò)了
???...?
??case?0:?//子進(jìn)程創(chuàng)建成功
???ngx_parent?=?ngx_pid;
???ngx_pid?=?ngx_getpid();
???proc(cycle,?data);
???break;
??default:
???break;
?}
?...
}
在 ngx_spawn_process 中調(diào)用 fork 來(lái)創(chuàng)建進(jìn)程,創(chuàng)建成功后 Worker 進(jìn)程就將進(jìn)入 ngx_worker_process_cycle 來(lái)進(jìn)行處理了。
總結(jié):在網(wǎng)絡(luò)上,master 進(jìn)程其實(shí)只是 listen 了一下。listen 過后的 socket 存到 cycle->listening 這里了。剩下的網(wǎng)絡(luò)操作都是在 Worker 中完成的。
二、Worker 進(jìn)程處理
在上面小節(jié)中看到,Master 進(jìn)程關(guān)于網(wǎng)絡(luò)其實(shí)做的事情不多,只是 bind 和 listen 了一下。epoll 相關(guān)的函數(shù)調(diào)用一個(gè)也沒見著,更別說 accept 接收連接,以及 read 、 write 函數(shù)處理了。那這些細(xì)節(jié)一定都是在 Worker 進(jìn)程中完成的。
事實(shí)的確如此,epoll_create、epoll_ctl、epoll_wait 都是在 Worker 進(jìn)程中執(zhí)行的。

在 Worker 進(jìn)程中,創(chuàng)建了一個(gè) epoll 內(nèi)核對(duì)象,通過 epoll_ctl 將其想監(jiān)聽的事件注冊(cè)上去,然后調(diào)用 epoll_wait 進(jìn)入事件循環(huán)。
//file:?src/os/unix/ngx_process_cycle.c
static?void?ngx_worker_process_cycle(ngx_cycle_t?*cycle,?void?*data)
{
?//2.2?Worker進(jìn)程初始化編譯進(jìn)來(lái)的各個(gè)模塊
?ngx_worker_process_init(cycle,?worker);
?//進(jìn)入事件循環(huán)
?for?(?;;?)?{
??//2.3?進(jìn)入?epollwait
??ngx_process_events_and_timers(cycle);
??......
?}
}
接下來(lái)我們分別來(lái)細(xì)看。
2.1 Nginx 的 網(wǎng)絡(luò)相關(guān) module
撇開 Worker 的工作流程不提,咱們先來(lái)了解一個(gè)背景知識(shí) - Nginx module。
Nginx 采用的是一種模塊化的架構(gòu),它的模塊包括核心模塊、標(biāo)準(zhǔn)HTTP模塊、可選HTTP模塊、郵件服務(wù)模塊和第三方模塊等幾大類。每一個(gè)模塊都以一個(gè) module 的形式存在,都對(duì)應(yīng)一個(gè) ngx_module_s 結(jié)構(gòu)體。通過這種方式來(lái)實(shí)現(xiàn)軟件可拔插,是一種非常優(yōu)秀的軟件架構(gòu)。

每個(gè) module 根據(jù)自己的需求來(lái)實(shí)現(xiàn)各種 init_xxx, exit_xxx 方法來(lái)供 Nginx 在合適的時(shí)機(jī)調(diào)用。
//file:?src/core/ngx_module.h
struct?ngx_module_s?{
?......
?ngx_uint_t????????????version;
?void?????????????????*ctx;
?ngx_command_t????????*commands;
?ngx_uint_t????????????type;
?ngx_int_t???????????(*init_master)(ngx_log_t?*log);
?ngx_int_t???????????(*init_module)(ngx_cycle_t?*cycle);
?ngx_int_t???????????(*init_process)(ngx_cycle_t?*cycle);
?ngx_int_t???????????(*init_thread)(ngx_cycle_t?*cycle);
?void????????????????(*exit_thread)(ngx_cycle_t?*cycle);
?void????????????????(*exit_process)(ngx_cycle_t?*cycle);
?void????????????????(*exit_master)(ngx_cycle_t?*cycle);
?......
};
其中和網(wǎng)絡(luò)相關(guān)的 module 有 ngx_events_module 、ngx_event_core_module 和具體的網(wǎng)絡(luò)底層模塊 ngx_epoll_module、ngx_kqueue_module等。
對(duì)于 ngx_epoll_module 來(lái)說,它在其上下文 ngx_epoll_module_ctx 中定義了各種 actions 方法(添加事件、刪除事件、添加連接等)。
//file:src/event/ngx_event.h
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;
}?ngx_event_module_t;
//file:src/event/modules/ngx_epoll_module.c
static?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?*/
#if?(NGX_HAVE_EVENTFD)
??ngx_epoll_notify,????????????????/*?trigger?a?notify?*/
#else
??NULL,????????????????????????????/*?trigger?a?notify?*/
#endif
??ngx_epoll_process_events,????????/*?process?the?events?*/
??ngx_epoll_init,??????????????????/*?init?the?events?*/
??ngx_epoll_done,??????????????????/*?done?the?events?*/
?}
};
其中有一個(gè) init 方法是 ngx_epoll_init,在這個(gè) init 中會(huì)進(jìn)行 epoll 對(duì)象的創(chuàng)建,以及 ngx_event_actions 方法的設(shè)置。
//file:src/event/modules/ngx_epoll_module.c
static?ngx_int_t
ngx_epoll_init(ngx_cycle_t?*cycle,?ngx_msec_t?timer)
{
?//創(chuàng)建一個(gè)?epoll?句柄
?ep?=?epoll_create(cycle->connection_n?/?2);
?...
?ngx_event_actions?=?ngx_epoll_module_ctx.actions;
}
2.2 Worker 進(jìn)程初始化各個(gè)模塊
Worker 進(jìn)程初始化的時(shí)候,在 ngx_worker_process_init 中讀取配置信息進(jìn)行一些設(shè)置,然后調(diào)用所有模塊的 init_process 方法。

來(lái)看詳細(xì)代碼。
//file:?src/os/unix/ngx_process_cycle.c
static?void
ngx_worker_process_init(ngx_cycle_t?*cycle,?ngx_int_t?worker)
{
?...
?//獲取配置
?ccf?=?(ngx_core_conf_t?*)?ngx_get_conf(cycle->conf_ctx,?ngx_core_module);
?//設(shè)置優(yōu)先級(jí)
?setpriority(PRIO_PROCESS,?0,?ccf->priority)
?//設(shè)置文件描述符限制
?setrlimit(RLIMIT_NOFILE,?&rlmt)
?setrlimit(RLIMIT_CORE,?&rlmt)
?//group?和?uid?設(shè)置
?initgroups(ccf->username,?ccf->group)
?setuid(ccf->user)
?//CPU親和性
?cpu_affinity?=?ngx_get_cpu_affinity(worker)
?if?(cpu_affinity)?{
??ngx_setaffinity(cpu_affinity,?cycle->log);
?}
?......
?//調(diào)用各個(gè)模塊的init_process進(jìn)行模塊初始化
?for?(i?=?0;?cycle->modules[i];?i++)?{
??if?(cycle->modules[i]->init_process)?{
???if?(cycle->modules[i]->init_process(cycle)?==?NGX_ERROR)?{
????/*?fatal?*/
????exit(2);
???}
??}
?}
?...
}
前面我們說過 ngx_event_core_module ,它的 init_process 方法是 ngx_event_process_init。
//file:?src/event/ngx_event.c
ngx_module_t??ngx_event_core_module?=?{
?...
?ngx_event_process_init,????????????????/*?init?process?*/
?...
};
在 ngx_event_core_module 的 ngx_event_process_init 中,我們將看到 Worker 進(jìn)程使用 epoll_create 來(lái)創(chuàng)建 epoll 對(duì)象,使用epoll_ctl 來(lái)監(jiān)聽 listen socket 上的連接請(qǐng)求。

來(lái)詳細(xì)看 ngx_event_process_init 的代碼。
//file:?src/event/ngx_event.c
static?ngx_int_t?ngx_event_process_init(ngx_cycle_t?*cycle)
{
?//調(diào)用模塊的init,創(chuàng)建?epoll?對(duì)象
?for?(m?=?0;?cycle->modules[m];?m++)?{
??if?(cycle->modules[m]->type?!=?NGX_EVENT_MODULE)?{
???continue;
??}
??...
??module->actions.init(cycle,?ngx_timer_resolution)
??break;
?}
?...
?//獲取自己監(jiān)聽的sokcet,將它們都添加到?epoll?中
?ngx_event_t?????????*rev
?ls?=?cycle->listening.elts;
?for?(i?=?0;?i?listening.nelts;?i++)?{
??//獲取一個(gè)?ngx_connection_t
??c?=?ngx_get_connection(ls[i].fd,?cycle->log);
??//設(shè)置回調(diào)函數(shù)為?ngx_event_accept
??rev->handler?=?ngx_event_accept?
??if?(ngx_add_event(rev,?NGX_READ_EVENT,?0)?==?NGX_ERROR)?{
???return?NGX_ERROR;
??}
?}
?...
}
通過 ngx_add_event 注冊(cè)的 READ 事件的處理函數(shù)。ngx_add_event 就是一個(gè)抽象,對(duì)于 epoll 來(lái)說就是對(duì) epoll_ctl 的封裝而已。
//file:?src/event/ngx_event.h
#define?ngx_add_event????????ngx_event_actions.add
//file:?src/event/modules/ngx_epoll_module.c
static?ngx_int_t?ngx_epoll_add_event(...)
{
?if?(epoll_ctl(ep,?op,?c->fd,?&ee)?==?-1)?{
?...
}
TODO: epoll_create 還沒解決呢。
2.3 進(jìn)入 epollwait
在 ngx_worker_process_init 中, epoll_create 和 epoll_ctl 都已經(jīng)完成了。接下來(lái)就是進(jìn)入事件循環(huán),執(zhí)行 epoll_wait 來(lái)處理。

//file:?src/event/ngx_event.c
void
ngx_process_events_and_timers(ngx_cycle_t?*cycle)
{
?...
?//?防accept驚群鎖
?if?(ngx_use_accept_mutex)?{
??//嘗試獲取鎖,獲取失敗直接返回
??if?(ngx_trylock_accept_mutex(cycle)?==?NGX_ERROR)?{
???return;
??}
??//獲取鎖成功,則設(shè)置?NGX_POST_EVENTS?標(biāo)記。
??if?(ngx_accept_mutex_held)?{
???flags?|=?NGX_POST_EVENTS;
??}?else?{
???...
??}
?}
?//處理各種事件
?(void)?ngx_process_events(cycle,?timer,?flags);
}
在 ngx_process_events_and_timers 開頭處,判斷是否使用 accpet_mutext 鎖。這是一個(gè)防止驚群的解決辦法。如果使用的話,先調(diào)用 ngx_trylock_accept_mutex 獲取鎖,獲取失敗則直接返回,過段時(shí)間再來(lái)嘗試。獲取成功是則設(shè)置 NGX_POST_EVENTS 的標(biāo)志位。
接下來(lái)調(diào)用 ngx_process_events 來(lái)處理各種網(wǎng)絡(luò)和 timer 事件。對(duì)于 epoll 來(lái)說,這個(gè)函數(shù)就是對(duì) epoll_wait 的封裝。
//file:?src/event/ngx_event.h
#define?ngx_process_events???ngx_event_actions.process_events
//file:?src/event/modules/ngx_epoll_module.c
static?ngx_int_t?ngx_epoll_process_events(ngx_cycle_t?*cycle,?...)
{
?events?=?epoll_wait(ep,?event_list,?(int)?nevents,?timer);
?for?(i?=?0;?i?
??if?(flags?&?NGX_POST_EVENTS)?{
???...
???ngx_post_event(rev,?queue);
??}else{
???//調(diào)用回調(diào)函數(shù)
???rev->handler(rev);
??}
??...
?}
}
可見,在 ngx_epoll_process_events 是調(diào)用 epoll_wait 等待各種事件的發(fā)生。如果沒有 NGX_POST_EVENTS 標(biāo)志,則直接回調(diào) rev->handler 進(jìn)行處理。使用了 accept_mutex 鎖的話,先把這個(gè)事件保存起來(lái),等后面合適的時(shí)機(jī)再去 accpet。
簡(jiǎn)單對(duì)本節(jié)內(nèi)容匯總一下。在 Master 進(jìn)程中只是做了 socket 的 bind 和 listen。 而在 Worker 進(jìn)程中所做的事情比較多,創(chuàng)建了 epoll,使用 epoll_ctl 將 listen 狀態(tài)的 socket 的事件監(jiān)控起來(lái)。最后調(diào)用 epoll_wait 進(jìn)入了事件循環(huán),開始處理各種網(wǎng)絡(luò)和 timer 事件。 本節(jié)流程總結(jié)如圖。

三、用戶連接來(lái)啦!
現(xiàn)在假設(shè)用戶的連接請(qǐng)求已經(jīng)到了,這時(shí)候 epoll_wait 返回后會(huì)執(zhí)行其對(duì)應(yīng)的 handler 函數(shù) ngx_add_event。

在該回調(diào)函數(shù)中被執(zhí)行到的時(shí)候,表示 listen 狀態(tài)的 socket 上面有連接到了。所以這個(gè)函數(shù)主要做了三件事。
1.調(diào)用 accept 獲取用戶連接
2.獲取 connection 對(duì)象,其回調(diào)函數(shù)為 ngx_http_init_connection
3.將新連接 socket 通過 epoll_ctl 添加到 epoll 中進(jìn)行管理
我們來(lái)看 ngx_event_accept 詳細(xì)代碼。
//file:?src/event/ngx_event_accept.c
void?ngx_event_accept(ngx_event_t?*ev)
{
?do?{
??//接收建立好的連接
??s?=?accept(lc->fd,?&sa.sockaddr,?&socklen);
??if?s?{
???//3.1?獲取?connection
???c?=?ngx_get_connection(s,?ev->log);
???//3.2?添加新連接
???if?(ngx_add_conn(c)?==?NGX_ERROR)?{
????ngx_close_accepted_connection(c);
????return;
???}
??}?
?}?while?(ev->available);
}
listen socket 上的讀事件發(fā)生的時(shí)候,就意味著有用戶連接就緒了。所以可以直接通過 accept 將其取出來(lái)。取出連接以后,再獲取一個(gè)空閑的 connection對(duì)象,通過 ngx_add_conn 將其添加到 epoll 中進(jìn)行管理。
3.1 獲取 connection
我們說一下 ngx_get_connection,這個(gè)函數(shù)本身倒是沒有啥可說的。就是從 ngx_cycle 的 free_connections 中獲取一個(gè) connection 出來(lái)。
//file:?src/core/ngx_connection.c
ngx_connection_t?*ngx_get_connection(ngx_socket_t?s,?ngx_log_t?*log)
{
?c?=?ngx_cycle->free_connections;
?c->read?=?rev;
?c->write?=?wev;
?c->fd?=?s;
?c->log?=?log;
?return?c
}
值得說的是 free_connections 中的連接,對(duì)于 HTTP 服務(wù)來(lái)說,會(huì)經(jīng)過 ngx_http_init_connection 的初始化處理。它會(huì)設(shè)置該連接讀寫事件的回調(diào)函數(shù) c->read->handler 和 c->write->handler。
//file:?src/http/ngx_http_request.c
void?ngx_http_init_connection(ngx_connection_t?*c)
{
?...
?rev?=?c->read;
?rev->handler?=?ngx_http_wait_request_handler;
?c->write->handler?=?ngx_http_empty_handler;
}
3.2 添加新連接
我們?cè)賮?lái)看 ngx_add_conn,對(duì)于 epoll module 來(lái)說,它就是 ngx_epoll_add_connection 這個(gè)函數(shù)。
//file:?src/event/ngx_event.h
#define?ngx_add_conn?????????ngx_event_actions.add_conn
//file:?src/event/modules/ngx_epoll_module.c
static?ngx_int_t
ngx_epoll_add_connection(ngx_connection_t?*c)
{
?struct?epoll_event??ee;
?ee.events?=?EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP;
?ee.data.ptr?=?(void?*)?((uintptr_t)?c?|?c->read->instance);
?epoll_ctl(ep,?EPOLL_CTL_ADD,?c->fd,?&ee)
?c->read->active?=?1;
?c->write->active?=?1;
?return?NGX_OK;
}
可見這只是 epoll_ctl 的一個(gè)封裝而已。這里再補(bǔ)充說一下,如果這個(gè)客戶端連接 socket 上有數(shù)據(jù)到達(dá)的時(shí)候,就會(huì)進(jìn)入到上面 3.1 節(jié)中注冊(cè)的 ngx_http_wait_request_handler 函數(shù)進(jìn)行處理。后面就是 HTTP 的處理邏輯了。
四、總結(jié)
Nginx 的 Master 中做的網(wǎng)絡(luò)相關(guān)動(dòng)作不多,僅僅只是創(chuàng)建了 socket、然后 bind 并 listen 了一下。接著就是用自己 fork 出來(lái)多個(gè) Worker 進(jìn)程來(lái)。由于每個(gè)進(jìn)程都一樣,所以每個(gè) Worker 都有 Master 創(chuàng)建出來(lái)的 listen 狀態(tài)的 socket 句柄。

Worker 進(jìn)程處理的網(wǎng)絡(luò)相關(guān)工作就比較多了。epoll_create、epoll_ctl、epoll_wait 都是在 Worker 進(jìn)程中執(zhí)行的,也包括用戶連接上的數(shù)據(jù) read、處理 和 write。

1.先是使用 epoll_create 創(chuàng)建一個(gè) epoll 對(duì)象出來(lái)
2.設(shè)置回調(diào)為 ngx_event_accept
3.通過 epoll_ctl 將所有 listen 狀態(tài)的 socket 的事件都管理起來(lái)
4.執(zhí)行 epoll_wait 等待 listen socket 上的連接到來(lái)
5.新連接到來(lái)是 epoll_wait 返回,進(jìn)入 ngx_event_accept 回調(diào)
6.ngx_event_accept 回調(diào)中將新連接也添加到 epoll 中進(jìn)行管理(其回調(diào)為ngx_http_init_connection)
7.繼續(xù)進(jìn)入 epoll_wait 等待事件
8.用戶數(shù)據(jù)請(qǐng)求到達(dá)時(shí)進(jìn)入 http 回調(diào)函數(shù)進(jìn)行處理
講到這里,你可以覺得咱們已經(jīng)討論完了。實(shí)際上有一個(gè)點(diǎn)我們還沒有考慮到。我們上面討論的流程是一個(gè) Worker 在工作的情況。那么在多 Worker 的情況下,Nginx 的全貌咱們還沒展開說過。通過上文我們可以看到以下幾個(gè)細(xì)節(jié):
1.每個(gè) Worker 都會(huì)有一個(gè)屬于自己的 epoll 對(duì)象
2.每個(gè) Worker 會(huì)關(guān)注所有的 listen 狀態(tài)上的新連接事件
3.對(duì)于用戶連接,只有一個(gè) Worker 會(huì)處理,其它 Worker 不會(huì)持有該用戶連接的 socket。
根據(jù)這三條結(jié)論,我們?cè)佼嬕粋€(gè) Nginx 的全貌圖。

好了,今天關(guān)于 Nginx 網(wǎng)絡(luò)原理的分享就到此結(jié)束。希望通過這個(gè)優(yōu)秀的軟件能給你的工作帶去一些啟發(fā)和思考,助力你的工作提升。
推薦閱讀
Nginx系列教程(一)| 手把手教你在Linux環(huán)境下搭建Nginx服務(wù)
Nginx系列教程(二)| 一文帶你讀懂Nginx的正向與反向代理
Nginx系列教程(三)| 一文帶你讀懂Nginx的負(fù)載均衡
Nginx系列教程(四)| 一文帶你讀懂Nginx的動(dòng)靜分離
Nginx系列教程(五)| 利用 Nginx+Keepalived 實(shí)現(xiàn)高可用技術(shù)
Nginx系列教程(六)| 手把手教你搭建 LNMP 架構(gòu)并部署天空網(wǎng)絡(luò)電影系統(tǒng)

