nginx源碼之epoll事件循環(huán)處理方式
要說(shuō)明的幾點(diǎn)
一、為了方便調(diào)試及跟蹤代碼所以采用了單進(jìn)程的模式運(yùn)行程序
二、自備一份源碼
三、在閱讀函數(shù)之前,我覺(jué)得要帶有幾個(gè)問(wèn)題去看
- 事件循環(huán)什么時(shí)候開(kāi)始?
- 怎么添加事件?
- 事件如何分發(fā)處理?
一、事件循環(huán)的創(chuàng)建
當(dāng)我們啟動(dòng)程序,會(huì)進(jìn)入下面函數(shù),顧名思義就是處理單進(jìn)程循環(huán)的函數(shù)
void ngx_single_process_cycle(ngx_cycle_t *cycle) { ngx_uint_t i; if (ngx_set_environment(cycle, NULL) == NULL) { /* fatal */ exit(2); } //調(diào)用每個(gè)模塊的初始化 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); } } } for ( ;; ) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle"); //處理定時(shí)器事件及網(wǎng)絡(luò)事件 ngx_process_events_and_timers(cycle); ... } }
在調(diào)用模塊的初始化時(shí)就會(huì)調(diào)用ngx_event_core_module模塊的ngx_event_process_init函數(shù)
ngx_event_process_init實(shí)現(xiàn)如下,隱藏部分不需要的代碼過(guò)程
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle) { ... for (m = 0; cycle->modules[m]; m++) { //只有ngx_epoll_module是NGX_EVENT_MODULE類型,epoll模塊 if (cycle->modules[m]->type != NGX_EVENT_MODULE) { continue; } if (cycle->modules[m]->ctx_index != ecf->use) { continue; } module = cycle->modules[m]->ctx; //初始化epoll ,創(chuàng)建epoll if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) { /* fatal */ exit(2); } break; } }
當(dāng)類型是NGX_EVENT_MODULE的模塊只有ngx_epoll_module和ngx_event_core_module,而只有ngx_epoll_module模塊的actions.init接口不為空,因此會(huì)調(diào)用ngx_epoll_init,在該函數(shù)里面掉了epoll_create 創(chuàng)建了epoll,并將值賦給全局的ep
static int ep = -1;
二、連接的創(chuàng)建
2.1 創(chuàng)建管理連接及其讀寫(xiě)事件的數(shù)組
依然在ngx_event_process_init函數(shù)中,數(shù)組大小為配置文件中 worker_connections 的值
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle) { ... cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log); if (cycle->connections == NULL) { return NGX_ERROR; } c = cycle->connections; //分配讀事件 cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log); if (cycle->read_events == NULL) { return NGX_ERROR; } rev = cycle->read_events; for (i = 0; i < cycle->connection_n; i++) { rev[i].closed = 1; rev[i].instance = 1; } //分配寫(xiě)事件 cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log); if (cycle->write_events == NULL) { return NGX_ERROR; } wev = cycle->write_events; for (i = 0; i < cycle->connection_n; i++) { wev[i].closed = 1; } }
2.2 創(chuàng)建一個(gè)空閑連接的鏈表
依然在ngx_event_process_init函數(shù)中,表示沒(méi)有使用的連接
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle) { ... do { i--; c[i].data = next; c[i].read = &cycle->read_events[i]; c[i].write = &cycle->write_events[i]; c[i].fd = (ngx_socket_t) -1; next = &c[i]; } while (i); cycle->free_connections = next; cycle->free_connection_n = cycle->connection_n; ... }
此時(shí)數(shù)據(jù)結(jié)構(gòu)如下
2.3 遍歷所有的cycle->listening元素,并為其添加讀事件
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle) { ... ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { ... rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept : ngx_event_recvmsg; ... if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { return NGX_ERROR; } ... } ... }
這里特別的說(shuō)明下cycle->listening在哪里設(shè)置?設(shè)置了什么信息?后面會(huì)有用。
三、http服務(wù)器端口的設(shè)置
在進(jìn)入事件循環(huán)之前,nginx會(huì)先對(duì)配置文件做解析處理,也就是ngx_conf_parse中,當(dāng)解析到http配置項(xiàng)時(shí),會(huì)調(diào)用ngx_http_module模塊的配置項(xiàng)處理函數(shù)ngx_http_block,在其中做了很多http相關(guān)的設(shè)置
3.1 cycle->listening元素的添加
函數(shù)調(diào)用過(guò)程如下:
ngx_http_block -> ngx_http_add_listening ->ngx_create_listening
最終調(diào)用ngx_create_listening
static ngx_listening_t * ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) { ngx_listening_t *ls; ... ls = ngx_create_listening(cf, addr->opt.sockaddr, addr->opt.socklen); if (ls == NULL) { return NULL; } ls->addr_ntop = 1; //設(shè)置ngx_listening_t的 接口handler ls->handler = ngx_http_init_connection; ... }
該函數(shù)做了2件事
- 將監(jiān)聽(tīng)信息放入cycle->listening數(shù)組
- 設(shè)置ngx_listening_t的 接口handler為ngx_http_init_connection(后面會(huì)用到)
下面是ngx_create_listening 函數(shù),用來(lái)說(shuō)明1
ngx_listening_t * ngx_create_listening(ngx_conf_t *cf, struct sockaddr *sockaddr, socklen_t socklen) { ... ls = ngx_array_push(&cf->cycle->listening); ... }
3.2 開(kāi)啟了服務(wù)器端口的監(jiān)聽(tīng)
調(diào)用了ngx_open_listening_sockets函數(shù),函數(shù)調(diào)用過(guò)程如下:
ngx_init_cycle -> ngx_open_listening_sockets
看到bind 和listen就應(yīng)該很快的反應(yīng)過(guò)來(lái)這是一個(gè)服務(wù)器端的常用代碼,所以是在這里開(kāi)啟了服務(wù)器端口的監(jiān)聽(tīng)
ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle) { ... for (i = 0; i < cycle->listening.nelts; i++) { if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) { ... } if (listen(s, ls[i].backlog) == -1) { ... } } ... }
至此,服務(wù)器監(jiān)聽(tīng)設(shè)置已經(jīng)完成了,同時(shí)每個(gè)需要監(jiān)聽(tīng)的端口到放入了cycle->listening。
我們?cè)倩氐?.3,epoll開(kāi)始將監(jiān)聽(tīng)端口的連接放入了epoll管理,當(dāng)客戶端來(lái)連接請(qǐng)求時(shí),就會(huì)觸發(fā)epoll讀事件,這里要注意的是,其中rev代表的是nginx定義的事件類。
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept: ngx_event_recvmsg;
四、執(zhí)行事件循環(huán)
讓我們繼續(xù)回到ngx_single_process_cycle函數(shù),當(dāng)模塊調(diào)用完init_process后,開(kāi)始進(jìn)入了一個(gè)死循環(huán)的過(guò)程,顯然這里應(yīng)該會(huì)是一個(gè)事件循環(huán)入口,而我們使用epoll,那代碼最終肯定會(huì)定格到 epoll_wait,那接下來(lái)的目的就是找到這個(gè)代碼段,繼續(xù)往下看代碼
for ( ;; ) { ngx_process_events_and_timers(cycle); }
ngx_process_events_and_timers函數(shù)如下:其中ngx_process_events 就是ngx_epoll_process_events函數(shù)
void ngx_process_events_and_timers(ngx_cycle_t *cycle) { ... //epoll 等待 (void) ngx_process_events(cycle, timer, flags); delta = ngx_current_msec - delta; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "timer delta: %M", delta); ngx_event_process_posted(cycle, &ngx_posted_accept_events); if (ngx_accept_mutex_held) { ngx_shmtx_unlock(&ngx_accept_mutex); } ngx_event_expire_timers(); ngx_event_process_posted(cycle, &ngx_posted_events); }
ngx_epoll_process_events函數(shù)如下,去掉了大部分跟邏輯無(wú)關(guān)的代碼,做了以下工作
- epoll_wait等待所有IO事件來(lái)臨
- 遍歷觸發(fā)的事件列表,調(diào)用其handler接口,在這里因?yàn)槲覀兪菃芜M(jìn)程,固沒(méi)有進(jìn)程鎖,flags 始終為0
static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) { events = epoll_wait(ep, event_list, (int) nevents, timer); ... for (i = 0; i < events; i++) { c = event_list[i].data.ptr; instance = (uintptr_t) c & 1; c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1); rev = c->read; revents = event_list[i].events; if ((revents & EPOLLIN) && rev->active) { rev->ready = 1; rev->available = -1; if (flags & NGX_POST_EVENTS) { queue = rev->accept ? &ngx_posted_accept_events : &ngx_posted_events; ngx_post_event(rev, queue); } else { rev->handler(rev); } } wev = c->write; if ((revents & EPOLLOUT) && wev->active) { if (flags & NGX_POST_EVENTS) { ngx_post_event(wev, &ngx_posted_events); } else { wev->handler(wev); } } } return NGX_OK; }
這就很明顯了,epoll事件循環(huán)體就在此,根據(jù)事件的觸發(fā)方式來(lái)區(qū)分讀寫(xiě)事件,因?yàn)閒lags為0,所以這里對(duì)事件的處理都是調(diào)用了nginx定義的事件結(jié)構(gòu)體ngx_event_t的handler接口,而不會(huì)調(diào)用ngx_post_event,因此接下來(lái)根據(jù)handler接口實(shí)現(xiàn)的不同來(lái)說(shuō)明連接的建立過(guò)程及讀寫(xiě)事件的處理
五、客戶端的連接的接入
先引入3.2節(jié)中描述的代碼,其中 c->type 就是SOCK_STREAM ,在其他地方賦值了,不在過(guò)多描述
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept: ngx_event_recvmsg;
所以調(diào)用了ngx_event_accept,那該函數(shù)做了哪些事?下面貼出一段代碼來(lái)說(shuō)明(忽略一些socket 的配置代碼)
void ngx_event_accept(ngx_event_t *ev) { ngx_listening_t *ls; ngx_connection_t *c, *lc; ... lc = ev->data; ls = lc->listening; ... c = ngx_get_connection(s, ev->log); ... c->recv = ngx_recv; c->send = ngx_send; c->recv_chain = ngx_recv_chain; c->send_chain = ngx_send_chain; c->log = log; c->pool->log = log; c->socklen = socklen; c->listening = ls; c->local_sockaddr = ls->sockaddr; c->local_socklen = ls->socklen; ... 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; } } ls->handler(c); ... }
5.1 取出一個(gè)未使用的連接,并將該連接納入epoll去管理
從連接池中拿出一個(gè)連接,在調(diào)用ngx_add_conn 宏,此時(shí)該連接已經(jīng)將socket的描述符fd設(shè)置進(jìn)去了其中ngx_add_conn 定義如下
#define ngx_add_conn ngx_event_actions.add_conn
而ngx_event_actions是一個(gè)全局的變量,在ngx_epoll_init函數(shù) 賦值
static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer) { ... ngx_event_actions = ngx_epoll_module_ctx.actions; ... }
因此ngx_add_conn就是ngx_epoll_add_connection函數(shù),就是將客戶端的連接納入epoll的管理,如下
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); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "epoll add connection: fd:%d ev:%08XD", c->fd, ee.events); if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) { ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, "epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd); return NGX_ERROR; } c->read->active = 1; c->write->active = 1; return NGX_OK; }
注意看,在上面函數(shù)中,epoll事件的data已經(jīng)指向了 nginx里面定義的連接類ngx_connection_t 了,這也就將epoll中的事件與nginx定義的ngx_connection_t 關(guān)聯(lián)起來(lái)了
至此,epoll開(kāi)始循環(huán)監(jiān)聽(tīng)每一個(gè)連接的事件了。
5.2 設(shè)置了連接的發(fā)送和接收函數(shù)
c->recv = ngx_recv; c->send = ngx_send;
這里的ngx_recv
//recv 函數(shù)宏定義 #define ngx_recv ngx_io.recv //ngx_io為全局定義 ngx_os_io_t ngx_io; //被該結(jié)構(gòu)體賦值,接收發(fā)送函數(shù)就很明顯了 ngx_os_io_t ngx_os_io = { ngx_unix_recv, ngx_readv_chain, ngx_udp_unix_recv, ngx_unix_send, ngx_udp_unix_send, ngx_udp_unix_sendmsg_chain, ngx_writev_chain, 0 }; ngx_unix_recv
5.3 調(diào)用監(jiān)聽(tīng)的連接類的接口handler
這個(gè)干了什么?有點(diǎn)懵。往前看 3.1節(jié),在解析配置文件時(shí),設(shè)置了該接口,其實(shí)就是ngx_http_init_connection函數(shù),在其中設(shè)置了 客戶端連接類的讀寫(xiě)事件接口分別是ngx_http_wait_request_handler 以及ngx_http_empty_handler,就不展開(kāi)了
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; }
總結(jié)
一:解析配置文件,確認(rèn)需要監(jiān)聽(tīng)的端口(配置信息),也就是需要listen幾個(gè)fd,同時(shí)設(shè)置該監(jiān)聽(tīng)端口的handler接口,將需要listen的端口信息放入cycle->listen,并設(shè)置了監(jiān)聽(tīng)接口體的接口為ngx_http_init_connection
二:調(diào)用epoll模塊的接口,初始化及創(chuàng)建epoll
三:將需要監(jiān)聽(tīng)的端口(一種描述的)納入epoll管理,同時(shí)設(shè)置其回調(diào)接口ngx_event_accept
四:當(dāng)檢測(cè)到客戶端的連接調(diào)用ngx_event_accept,在該函數(shù)中將客戶端的連接又納入epoll管理(其實(shí)這是epoll模型的一種常用的寫(xiě)法),同時(shí)調(diào)用了ngx_http_init_connection函數(shù),該函數(shù)設(shè)置了客戶端連接的讀寫(xiě)事件處理接口handler
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Nginx設(shè)置wordpress偽靜態(tài)的方法示例
偽靜態(tài)是相對(duì)真實(shí)靜態(tài)來(lái)講的,通常我們?yōu)榱嗽鰪?qiáng)搜索引擎的友好面,這篇文章主要介紹了Nginx設(shè)置wordpress偽靜態(tài)的方法示例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-09-09nginx有哪些常規(guī)調(diào)優(yōu)手段詳解
性能調(diào)優(yōu)就是用更少的資源提供更好的服務(wù),成本利益最大化,下面這篇文章主要給大家介紹了關(guān)于nginx有哪些常規(guī)調(diào)優(yōu)手段的相關(guān)資料,需要的朋友可以參考下2023-01-01nginx server_name配置多個(gè)域名時(shí)的坑
Nginx配置多個(gè)server_name時(shí),$server_name默認(rèn)取第一個(gè)值,導(dǎo)致PHP獲取錯(cuò)誤,下面就來(lái)介紹一下該問(wèn)題的解決,感興趣的可以了解一下2025-05-05Nginx配置跨域請(qǐng)求Access-Control-Allow-Origin * 詳解
這篇文章主要給大家介紹了關(guān)于Nginx配置跨域請(qǐng)求Access-Control-Allow-Origin * 的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Nginx具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06詳解Nginx防盜鏈和Nginx訪問(wèn)控制與Nginx解析php的配置
這篇文章主要介紹了詳解Nginx防盜鏈和Nginx訪問(wèn)控制與Nginx解析php的配置的相關(guān)資料,這里提供實(shí)例幫助大家,學(xué)習(xí)理解這部分內(nèi)容,需要的朋友可以參考下2017-08-08nginx訪問(wèn)日志并刪除指定天數(shù)前的日志記錄配置方法
這篇文章主要介紹了nginx訪問(wèn)日志并刪除指定天數(shù)前的日志記錄配置方法,需要的朋友可以參考下2014-03-03