nginx事件模組 -- 第三篇

鄭爾多斯發表於2018-12-23

微信公眾號:鄭爾多斯
關注可瞭解更多的Nginx知識。任何問題或建議,請公眾號留言;
關注公眾號,有趣有內涵的文章第一時間送達!

內容回顧

前面的幾篇文章中,我們介紹了nginx的事件模組的基礎知識。我們知道nginx中包含了三個與事件相關的module,分別為ngx_event_modulengx_event_core_module,ngx_epoll_module。我們也分別分析了這三個模組的配置檔案的解析過程,從本篇文章開始,將詳細分析nginx的事件處理過程,比如如何新增事件,刪除事件,處理事件等。

事件處理的概述

事件處理模組的主要解決的問題是如何收集,分發以及管理事件。在nginx中,事件主要包含網路事件和定時器事件。當然還有其他事件,比如master程式和worker程式通訊也是使用事件機制來完成的,但是我們的關注點主要在前兩種事件,第三種事件以後會分析。前面我們分析了nginx是如何解析配置檔案中與事件有關的配置項的,我們知道,當遇到events{}的時候,表示開始處理事件配置項了。

事件和連線

作為web伺服器,每一個使用者請求至少對應一個TCP連線,每個連線都包含一個毒事件和一個寫事件。這樣epoll就可以根據觸發的事件型別來排程相應的模組進行處理。Nginx連線分為主動連線和被動連線。分別用ngx_connection_tngx_peer_connection_t結構體來表示。

主動連線: 客戶端發起,伺服器被動接受的連線
被動連線: Nginx作為客戶端,向上遊伺服器發起的連線

本系列文章所介紹的連線都是被動連線,即Nginx作為伺服器被動的接受客戶端發起的連線。

Nginx連線池

為了提高效率,Nginx在啟動的時候,會建立好連線池和對應的讀寫事件,並且將每個連線都和對應的讀寫事件對應起來。這樣的話,在後面使用到連線的時候,只需要從連線池中獲取一個空閒的連線就行了。那麼Nginx是在什麼時候建立了連線池和讀寫事件呢?
前面兩篇文章我們介紹了ngx_events_module, ngx_event_core_module, ngx_epoll_module的配置項解析過程,以及create_conf(), init_conf()方法。我們知道ngx_module_t結構體有中有許多鉤子函式,其中有一個鉤子函式為init_process(),這個方法會在Nginx啟動過程中呼叫。在master/worker模式下,當建立了worker程式之後,會在worker程式的初始化過程中呼叫init_process()
通過原始碼可以發現,與事件機制相關的三個模組中,只有ngx_event_core_module有一個init_process()鉤子函式,為ngx_event_process_init()。其他兩個模組都沒有對應的鉤子函式。
具體的呼叫過程如下:

呼叫init_process()方法
呼叫init_process()方法

ngx_event_process_init

下面我們就分析一下這個ngx_event_process_init()這個方法。

  1static ngx_int_t
2ngx_event_process_init(ngx_cycle_t *cycle)
3
{
4    ngx_uint_t           m, i;
5    ngx_event_t         *rev, *wev;
6    ngx_listening_t     *ls;
7    ngx_connection_t    *c, *next, *old;
8    ngx_core_conf_t     *ccf;
9    ngx_event_conf_t    *ecf;
10    ngx_event_module_t  *module;
11
12    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
13    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
14
15    if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
16        ngx_use_accept_mutex = 1;
17        ngx_accept_mutex_held = 0;
18        ngx_accept_mutex_delay = ecf->accept_mutex_delay;
19
20    } else {
21        ngx_use_accept_mutex = 0;
22    }
23
24    ngx_queue_init(&ngx_posted_accept_events);
25    ngx_queue_init(&ngx_posted_events);
26
27    if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
28        return NGX_ERROR;
29    }
30
31    for (m = 0; cycle->modules[m]; m++) {
32        if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
33            continue;
34        }
35
36        if (cycle->modules[m]->ctx_index != ecf->use) {
37            continue;
38        }
39
40        module = cycle->modules[m]->ctx;
41
42        if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
43            /* fatal */
44            exit(2);
45        }
46
47        break;
48    }
49
50#if !(NGX_WIN32)
51
52    if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
53        struct sigaction  sa;
54        struct itimerval  itv;
55
56        ngx_memzero(&sa, sizeof(struct sigaction));
57        sa.sa_handler = ngx_timer_signal_handler;
58        sigemptyset(&sa.sa_mask);
59
60        if (sigaction(SIGALRM, &sa, NULL) == -1) {
61            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
62                          "sigaction(SIGALRM) failed");
63            return NGX_ERROR;
64        }
65
66        itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
67        itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
68        itv.it_value.tv_sec = ngx_timer_resolution / 1000;
69        itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;
70
71        if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
72            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
73                          "setitimer() failed");
74        }
75    }
76
77    if (ngx_event_flags & NGX_USE_FD_EVENT) {
78        struct rlimit  rlmt;
79
80        if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
81            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
82                          "getrlimit(RLIMIT_NOFILE) failed");
83            return NGX_ERROR;
84        }
85
86        cycle->files_n = (ngx_uint_t) rlmt.rlim_cur;
87
88        cycle->files = ngx_calloc(sizeof(ngx_connection_t *) * cycle->files_n,
89                                  cycle->log);
90        if (cycle->files == NULL) {
91            return NGX_ERROR;
92        }
93    }
94
95#else
96
97    if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
98        ngx_log_error(NGX_LOG_WARN, cycle->log0,
99                      "the \"timer_resolution\" directive is not supported "
100                      "with the configured event method, ignored");
101        ngx_timer_resolution = 0;
102    }
103
104#endif
105
106    cycle->connections =
107        ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
108    if (cycle->connections == NULL) {
109        return NGX_ERROR;
110    }
111
112    c = cycle->connections;
113
114    cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
115                                   cycle->log);
116    if (cycle->read_events == NULL) {
117        return NGX_ERROR;
118    }
119
120    rev = cycle->read_events;
121    for (i = 0; i < cycle->connection_n; i++) {
122        rev[i].closed = 1;
123        rev[i].instance = 1;
124    }
125
126    cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
127                                    cycle->log);
128    if (cycle->write_events == NULL) {
129        return NGX_ERROR;
130    }
131
132    wev = cycle->write_events;
133    for (i = 0; i < cycle->connection_n; i++) {
134        wev[i].closed = 1;
135    }
136
137    i = cycle->connection_n;
138    next = NULL;
139
140    do {
141        i--;
142
143        c[i].data = next;
144        c[i].read = &cycle->read_events[i];
145        c[i].write = &cycle->write_events[i];
146        c[i].fd = (ngx_socket_t-1;
147
148        next = &c[i];
149    } while (i);
150
151    cycle->free_connections = next;
152    cycle->free_connection_n = cycle->connection_n;
153
154    /* for each listening socket */
155
156    ls = cycle->listening.elts;
157    for (i = 0; i < cycle->listening.nelts; i++) {
158
159#if (NGX_HAVE_REUSEPORT)
160        if (ls[i].reuseport && ls[i].worker != ngx_worker) {
161            continue;
162        }
163#endif
164
165        c = ngx_get_connection(ls[i].fd, cycle->log);
166
167        if (c == NULL) {
168            return NGX_ERROR;
169        }
170
171        c->type = ls[i].type;
172        c->log = &ls[i].log;
173
174        c->listening = &ls[i];
175        ls[i].connection = c;
176
177        rev = c->read;
178
179        rev->log = c->log;
180        rev->accept = 1;
181
182#if (NGX_HAVE_DEFERRED_ACCEPT)
183        rev->deferred_accept = ls[i].deferred_accept;
184#endif
185
186        if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
187            if (ls[i].previous) {
188
189                /*
190                 * delete the old accept events that were bound to
191                 * the old cycle read events array
192                 */

193
194                old = ls[i].previous->connection;
195
196                if (ngx_del_event(old->read, NGX_READ_EVENT, NGX_CLOSE_EVENT)
197                    == NGX_ERROR)
198                {
199                    return NGX_ERROR;
200                }
201
202                old->fd = (ngx_socket_t-1;
203            }
204        }
205
206#if (NGX_WIN32)
207#else
208        rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
209                                                : ngx_event_recvmsg;
210#if (NGX_HAVE_REUSEPORT)
211
212        if (ls[i].reuseport) {
213            if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
214                return NGX_ERROR;
215            }
216
217            continue;
218        }
219
220#endif
221
222        if (ngx_use_accept_mutex) {
223            continue;
224        }
225
226#if (NGX_HAVE_EPOLLEXCLUSIVE)
227
228        if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
229            && ccf->worker_processes > 1)
230        {
231            if (ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)
232                == NGX_ERROR)
233            {
234                return NGX_ERROR;
235            }
236
237            continue;
238        }
239
240#endif
241
242        if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
243            return NGX_ERROR;
244        }
245
246#endif
247
248    }
249
250    return NGX_OK;
251}
複製程式碼

這個函式的主要邏輯如下;

  • 獲取我們的全域性ngx_core_conf_t配置結構體和事件模組的配置結構體ngx_event_conf_t
  • 初始化ngx_posted_accept_eventsngx_posted_events,這兩個都是佇列,用來儲存需要滯後處理的連線和事件。
  • 初始化時間紅黑樹
  • 遍歷所有的Nginx模組,找到我們選擇的事件模組,比如ngx_epoll模組,然後呼叫該事件模組的init()方法(該方法下一篇檔案進行分析)
  • 對時間引數進行處理(之後分析這部分)
  • connection, read event, write event進行處理
  • cycle->listening中的監聽埠分配connection



下面我們看一下connection,read event,write event`的生成邏輯,如下:

 1cycle->connections =
2        ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
3    if (cycle->connections == NULL) {
4        return NGX_ERROR;
5    }
6
7    c = cycle->connections;
8
9    cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
10                                   cycle->log);
11    if (cycle->read_events == NULL) {
12        return NGX_ERROR;
13    }
14
15    rev = cycle->read_events;
16    for (i = 0; i < cycle->connection_n; i++) {
17        rev[i].closed = 1;
18        rev[i].instance = 1;
19    }
20
21    cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
22                                    cycle->log);
23    if (cycle->write_events == NULL) {
24        return NGX_ERROR;
25    }
26
27    wev = cycle->write_events;
28    for (i = 0; i < cycle->connection_n; i++) {
29        wev[i].closed = 1;
30    }
31
32    i = cycle->connection_n;
33    next = NULL;
34
35    do {
36        i--;
37
38        c[i].data = next;
39        c[i].read = &cycle->read_events[i];
40        c[i].write = &cycle->write_events[i];
41        c[i].fd = (ngx_socket_t-1;
42
43        next = &c[i];
44    } while (i);
45
46    cycle->free_connections = next;
47    cycle->free_connection_n = cycle->connection_n;
複製程式碼

上面的程式碼很簡單,就是根據配置檔案中的引數,生成對應數量的連線數,並根據下標將連線和讀寫事件相關聯。並把空閒連線的放置在free_connection連結串列中


喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達

鄭爾多斯
鄭爾多斯

相關文章