接上篇,这篇主要来看nginx的网络部分的初始化
首先是ngx_http_optimize_servers函数,这个函数是在ngx_http_block中被调用的,它的主要功能就是创建listening结构,然后初始化。这里ngx_listening_t表示一个正在监听的句柄以及它的上下文。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| static ngx_int_t ngx_http_optimize_servers(ngx_conf_t \*cf, ngx_http_core_main_conf_t \*cmcf, ngx_array_t *ports) { ngx_uint_t p, a; ngx_http_conf_port_t *port; ngx_http_conf_addr_t *addr;
if (ports == NULL) { return NGX_OK; }
port = ports->elts; for (p = 0; p < ports->nelts; p++) { ………………………………………….. //初始化listen结构 if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) { return NGX_ERROR; } }
return NGX_OK; }
|
通过上面可以看到核心的处理都是在ngx_http_init_listening中的,我们来看它的代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| static ngx_int_t ngx_http_init_listening(ngx_conf_t \*cf, ngx_http_conf_port_t \*port) { ………………………………. while (i < last) {
if (bind_wildcard && !addr[i].opt.bind) { i++; continue; } //这个函数里面将会创建,并且初始化listen结构 ls = ngx_http_add_listening(cf, &addr[i]); if (ls == NULL) { return NGX_ERROR; } ……………………………………………… switch (ls->sockaddr->sa_family) {
#if (NGX_HAVE_INET6) case AF_INET6: if (ngx_http_add_addrs6(cf, hport, addr) != NGX_OK) { return NGX_ERROR; } break; #endif default: /\* AF_INET \*/ //初始化虚拟主机相关的地址,设置hash等等. if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) { return NGX_ERROR; } break; }
addr++; last–; } ……………………. }
|
然后就来看真正做事的ngx_http_add_listening函数。这里我们只关注最重要的部分, 就是设置listen 的handler,要注意这个handler并不是accept handler,而是当accpet完之后,处理accept到的句柄的操作.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| static ngx_listening_t * ngx_http_add_listening(ngx_conf_t \*cf, ngx_http_conf_addr_t \*addr) { ngx_listening_t *ls; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; //创建listen结构体 ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen); if (ls == NULL) { return NULL; }
ls->addr_ntop = 1; //设置listen句柄的回调 ls->handler = ngx_http_init_connection; ……………………………………….. //设置对应的属性,backlog,读写buf. ls->backlog = addr->opt.backlog; ls->rcvbuf = addr->opt.rcvbuf; ls->sndbuf = addr->opt.sndbuf; ……………………………………………….. }
|
可是我们注意到在上面的函数中并没有创建listen socket句柄,而且也没有将句柄挂载到事件驱动中,这些动作都是在后面进行的,我们慢慢来看。
ngx_http_block结束后,也就是继续ngx_init_cycle下面的处理, 下面这部分就是创建socket,然后设置flag,最终绑定到listen结构体中.
1 2 3 4 5 6 7 8 9 10 11 12
| if (ngx_open_listening_sockets(cycle) != NGX_OK) { goto failed; }
if (!ngx_test_config) { ngx_configure_listening_sockets(cycle); }
|
当这些都结束后,就该挂载listen 句柄到事件处理器里面了。这个动作是在 ngx_worker_process_init中进行的,这个函数是在子进程被fork出来之后马上被调用的。而在这个函数中会执行所有模块的init_process方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->init_process) { //进程初始化 if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) { /\* fatal \*/ exit(2); } } }
|
而nginx的event模块包含一个init_process,也就是ngx_event_process_init.这个函数就是nginx的驱动器,他初始化事件驱动器,连接池,定时器,以及挂在listen 句柄的回调函数。
这个函数比较长,我们来一段段的看。
下面这一段是判断是否使用mutex锁,主要是为了控制负载均衡,nginx多进程的负载均衡我前面的blog有介绍过。这里要注意这个变量,因为下面还会用到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) { //使用mutex控制进程的负载均衡. ngx_use_accept_mutex = 1; ngx_accept_mutex_held = 0; ngx_accept_mutex_delay = ecf->accept_mutex_delay;
} else { ngx_use_accept_mutex = 0; }
|
下面这段是初始化定时器以及event module(epoll etc).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| //定时器初始化 if (ngx_event_timer_init(cycle->log) == NGX_ERROR) { return NGX_ERROR; }
//event module的初始化. 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; }
|
下面这段是初始化连接池,以及对应的读写事件的初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
| //创建连接池 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; //防止stale event rev[i].instance = 1; #if (NGX_THREADS) rev[i].lock = &c[i].lock; rev[i].own_lock = &c[i].lock; #endif } //创建写事件 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; #if (NGX_THREADS) wev[i].lock = &c[i].lock; wev[i].own_lock = &c[i].lock; #endif }
i = cycle->connection_n; next = NULL; //初始化连接池 do { i–; //链表 c[i].data = next; //每一个连接的读写事件对应cycle的读写事件 c[i].read = &cycle->read_events[i]; c[i].write = &cycle->write_events[i]; c[i].fd = (ngx_socket_t) -1;
next = &c[i];
#if (NGX_THREADS) c[i].lock = 0; #endif } while (i); //设置free 连接 cycle->free_connections = next; cycle->free_connection_n = cycle->connection_n;
|
下面这段初始化listen 事件 ,创建socket句柄,绑定事件回调,然后加入到事件驱动中,这里比较关键的就是如果使用了ngx_use_accept_mutex,则现在不会将事件加入到epoll中,而是等到在ngx_process_events_and_timers中将句柄加入,这是因为nginx为了防止惊群,采取了串行化处理accpet,也就是同时只有一个listen句柄会休眠在epoll_wait上等待连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| ls = cycle->listening.elts; //开始遍历listen for (i = 0; i < cycle->listening.nelts; i++) { //从连接池取得连接 c = ngx_get_connection(ls[i].fd, cycle->log);
if (c == NULL) { return NGX_ERROR; }
c->log = &ls[i].log;
c->listening = &ls[i]; ls[i].connection = c;
rev = c->read;
rev->log = c->log; rev->accept = 1; ………………………………. //设置listen句柄的事件回调,这个回调里面会accept,然后进行后续处理,这个函数是nginx事件驱动的第一个函数 rev->handler = ngx_event_accept; //如果默认使用mutex,则会继续下面操作。 if (ngx_use_accept_mutex) { continue; }
if (ngx_event_flags & NGX_USE_RTSIG_EVENT) { if (ngx_add_conn(c) == NGX_ERROR) { return NGX_ERROR; }
} else { //加可读事件到事件处理 if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { return NGX_ERROR; } } }
|
然后就是ngx_trylock_accept_mutex代码,这个我以前已经分析过了,这里就不详细分析了,只要知道它会获得一把锁,然后调用ngx_enable_accept_events将listen 事件加入到事件驱动框架中.
在接下来就是进程初始化的操作了,初始化完进程,然后休眠在epoll_wait上等待连接的到来,这部分代码我在前面的nginx进程模型里面有分析.