nginx源码分析之网络初始化
nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化。
从配置文件中读取初始化信息
与网络有关的配置命令主要有两个:listen和sever_name。首先先了解这两个命令的用法。
listen
listen命令设置nginx监听地址,nginx从这里接受请求。对于IP协议,这个地址就是address和port;对于UNIX域套接字协议,这个地址就是path。 一条listen指令只能指定一个address或者port。 address也可以是主机名。 比如:
1 listen 127.0.0.1:8000;
2 listen 127.0.0.1;
3 listen 8000;
4 listen *:8000;
5 listen localhost:8000;
IPv6地址用方括号来表示:
1 listen [::]:8000; 2 listen [fe80::1];
UNIX域套接字则使用“unix:”前缀:
1 listen unix:/var/run/nginx.sock;
如果只定义了address,nginx将使用80端口。在没有定义listen指令的情况下,如果以超级用户权限运行nginx,它将监听*:80,否则他将监听*:8000。如果listen指令携带default_server参数,当前虚拟主机将成为指定address:port的默认虚拟主机。 如果任何listen指令都没有携带default_server参数,那么第一个监听address:port的虚拟主机将被作为这个地址的默认虚拟主机。之所以会有默认虚拟主机,是由于同一个address:port可能会隶属于很多个虚拟主机,而区分这些虚拟主机则是用server_name指定各个虚拟主机的主机名。
可以为listen指令定义若干额外的参数,这些参数用于套接字相关的系统调用。 这些参数可以在任何listen指令中指定,但是对于每个address:port,只能定义一次。具体参数看以到nginx帮助文档中查到,这里就不再说明了。
更详细的介绍:http://nginx.org/cn/docs/http/ngx_http_core_module.html#listen
server_name
listen指令描述虚拟主机接受连接的地址和端口,用server_name指令列出虚拟主机的所有主机名。
设置虚拟主机名,比如:
1 server { 2 server_name example.com www.example.com; 3 }
第一个名字成为虚拟主机的首要主机名。
主机名中可以含有星号(“ *
”),以替代名字的开始部分或结尾部分:
1 server { 2 server_name example.com *.example.com www.example.*; 3 }
也可以在主机名中使用正则表达式,就是在名字前面补一个波浪线(“~”):
1 server { 2 server_name www.example.com ~^www\d+\.example\.com$; 3 }
更详细的介绍:http://nginx.org/cn/docs/http/server_names.html
了解了这两个命令的用法后,下面来看下源码中处理这两个命令的函数ngx_http_core_listen和ngx_http_core_server_name,这两个函数都在ngx_http_core_module.c文件中定义,也就是说这两个命令属于模块ngx_http_core_module。
1 static char *
2 ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
3 {
4 ...
5 cscf->listen = 1;
6 value = cf->args->elts;
7 ngx_memzero(&u, sizeof(ngx_url_t));
8
9 u.url = value[1];
10 u.listen = 1;
11 u.default_port = 80;
12 //解析listen命令后面的参数,ip:port
13 if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
14 if (u.err) {
15 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
16 "%s in \"%V\" of the \"listen\" directive",
17 u.err, &u.url);
18 }
19 return NGX_CONF_ERROR;
20 }
21 //根据上面解析的参数初始化ngx_http_listen_opt_t结构
22 ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
23 ngx_memcpy(&lsopt.u.sockaddr, u.sockaddr, u.socklen);
24 lsopt.socklen = u.socklen;
25 ...
26 //解析其它参数,如default_server,bind等,并通过这些参数设置lsopt
27 ...
28 //将解析到的虚拟主机的地址信息加入到监听列表中
29 if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) {
30 return NGX_CONF_OK;
31 }
32 return NGX_CONF_ERROR;
33 }
从ngx_http_core_listen函数代码可以看出,ngx_http_add_listen函数是其主要的部分,下面看下ngx_http_add_listen:
1 ngx_int_t
2 ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
3 ngx_http_listen_opt_t *lsopt)
4 {
5 ...
6 //获取ngx_http_core_module的main配置结构
7 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
8 if (cmcf->ports == NULL) { //初始化ports数组
9 cmcf->ports = ngx_array_create(cf->temp_pool, 2,
10 sizeof(ngx_http_conf_port_t));
11 if (cmcf->ports == NULL) {
12 return NGX_ERROR;
13 }
14 }
15 sa = &lsopt->u.sockaddr;
16 ... //解析协议
17 port = cmcf->ports->elts; //查看已经注册的port,是否新加入地址信息中的port已经存在了
18 for (i = 0; i < cmcf->ports->nelts; i++) {
19 if (p != port[i].port || sa->sa_family != port[i].family) {
20 continue;
21 }
22 //port已经存在了,将地址信息加入到这个port的地址列表中
23 return ngx_http_add_addresses(cf, cscf, &port[i], lsopt);
24 }
25 //port不存在,将新的port加入到ports数组中
26 port = ngx_array_push(cmcf->ports);
27 if (port == NULL) {
28 return NGX_ERROR;
29 }
30 port->family = sa->sa_family;
31 port->port = p;
32 port->addrs.elts = NULL;
33 return ngx_http_add_address(cf, cscf, port, lsopt); //将地址信息加入到对应port的地址列表中,一个port可以对应过个地址
34 }
ngx_http_add_listen中调用了ngx_http_add_addresses和ngx_http_add_address函数,先看下ngx_http_add_addresses:
1 static ngx_int_t 2 ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, 3 ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt) 4 { 5 ... 6 sa = &lsopt->u.sockaddr; 7 ... 8 p = lsopt->u.sockaddr_data + off; 9 addr = port->addrs.elts; 10 for (i = 0; i < port->addrs.nelts; i++) { 11 if (ngx_memcmp(p, addr[i].opt.u.sockaddr_data + off, len) != 0) { 12 continue; 13 } 14 //新加入的地址已经在地址列表中存在了,将新的虚拟主机信息加入到这个地址的虚拟主机列表中 15 if (ngx_http_add_server(cf, cscf, &addr[i]) != NGX_OK) { 16 return NGX_ERROR; 17 } 18 default_server = addr[i].opt.default_server; 19 if (lsopt->set) { //新的虚拟主机信息中设置了其它参数 20 21 if (addr[i].opt.set) { 22 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 23 "duplicate listen options for %s", addr[i].opt.addr); 24 return NGX_ERROR; 25 } 26 27 addr[i].opt = *lsopt; 28 } 29 if (lsopt->default_server) { //新的虚拟主机被设置为默认主机 30 31 if (default_server) { 32 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 33 "a duplicate default server for %s", addr[i].opt.addr); 34 return NGX_ERROR; 35 } 36 default_server = 1; 37 addr[i].default_server = cscf; 38 } 39 addr[i].opt.default_server = default_server; 40 ... 41 return NGX_OK; 42 } 43 //添加新地址信息到port的地址列表中 44 return ngx_http_add_address(cf, cscf, port, lsopt); 45 }
ngx_http_add_addresses函数中如果address:port都已经存在了,则调用ngx_http_add_server将新的虚拟主机的配置加入到address:port对应的虚拟主机列表中,由于一个address:port是可以对应多个虚拟主机的。如果address:port不存在,则调用ngx_http_add_address,将新的address加入到port地址列表中。下面看下ngx_http_add_address函数:
1 ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
2 ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
3 {
4 ngx_http_conf_addr_t *addr;
5 //初始化port地址列表
6 if (port->addrs.elts == NULL) {
7 if (ngx_array_init(&port->addrs, cf->temp_pool, 4,
8 sizeof(ngx_http_conf_addr_t))
9 != NGX_OK)
10 {
11 return NGX_ERROR;
12 }
13 }
14 ...
15 //将新地址加入到地址列表中
16 addr = ngx_array_push(&port->addrs);
17 if (addr == NULL) {
18 return NGX_ERROR;
19 }
20 addr->opt = *lsopt;
21 addr->hash.buckets = NULL;
22 addr->hash.size = 0;
23 addr->wc_head = NULL;
24 addr->wc_tail = NULL;
25 addr->default_server = cscf;
26 addr->servers.elts = NULL;
27 //将新的虚拟主机信息加入到这个地址的虚拟主机列表中
28 return ngx_http_add_server(cf, cscf, addr);
29 }
这个函数代码很简单,初始化地址列表,并调用ngx_http_add_server将新的虚拟主机的配置加入到address:port对应的虚拟主机列表中。下面再看下ngx_http_add_server函数:
1 static ngx_int_t
2 ngx_http_add_server(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
3 ngx_http_conf_addr_t *addr)
4 {
5 ...
6 server = ngx_array_push(&addr->servers); //添加新的虚拟主机配置
7 if (server == NULL) {
8 return NGX_ERROR;
9 }
10 *server = cscf;
11 return NGX_OK;
12 }
上面的对listen指令的处理函数基本分析完了,接下来再分析server_name指令对应的函数ngx_http_core_server_name:
1 static char * 2 ngx_http_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 3 { 4 ... 5 value = cf->args->elts; 6 for (i = 1; i < cf->args->nelts; i++) { 7 ... 8 sn = ngx_array_push(&cscf->server_names); 9 if (sn == NULL) { 10 return NGX_CONF_ERROR; 11 } 12 ... 13 sn->name = value[i]; 14 ... 15 }
这个函数主要是把server_name命令后面各个主机名放到当前虚拟主机配置的server_names数组中。
分析到这里,已经将配置文件中所有虚拟主机配置信息都读取到ngx_http_core_module模块的配置信息的ports中。在http命令的处理函数ngx_http_block最后调用了函数ngx_http_optimize_servers对上面的配置信息做了优化,下面具体来看下这个函数:
1 static ngx_int_t
2 ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
3 ngx_array_t *ports)
4 {
5 ...
6 port = ports->elts;
7 for (p = 0; p < ports->nelts; p++) {
8
9 ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
10 sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);
11 addr = port[p].addrs.elts;
12 for (a = 0; a < port[p].addrs.nelts; a++) {
13 ...
14 if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
15 return NGX_ERROR;
16 }
17 }
18 if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
19 return NGX_ERROR;
20 }
21 }
22 return NGX_OK;
23 }
这个函数先对每个address:port调用ngx_http_server_names函数,然后对每个port调用ngx_http_init_listening函数。下面看看ngx_http_server_names函数:
1 static ngx_int_t
2 ngx_http_server_names(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
3 ngx_http_conf_addr_t *addr)
4 {
5 ...
6 if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) {
7 goto failed;
8 }
9 cscfp = addr->servers.elts;
10 for (s = 0; s < addr->servers.nelts; s++) {
11 //每个server_name后面会带有多个域名
12 name = cscfp[s]->server_names.elts;
13 for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
14 ...
15 rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server,
16 NGX_HASH_WILDCARD_KEY);
17 ...
18 }
19 }
20 }
21 ...
22 if (ha.keys.nelts) { //无通配
23 hash.hash = &addr->hash; //非通配hash
24 hash.temp_pool = NULL;
25
26 if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) {
27 goto failed;
28 }
29 }
30 if (ha.dns_wc_head.nelts) { //前缀通配
31 ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts,
32 sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
33
34 hash.hash = NULL; //使用通配hash
35 hash.temp_pool = ha.temp_pool;
36 if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts,
37 ha.dns_wc_head.nelts)
38 ...
39 addr->wc_head = (ngx_hash_wildcard_t *) hash.hash;
40 }
41 if (ha.dns_wc_tail.nelts) { //后缀通配
42 ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts,
43 sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
44 hash.hash = NULL; //使用通配hash
45 hash.temp_pool = ha.temp_pool;
46 if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts,
47 ha.dns_wc_tail.nelts)
48 ...
49 addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash;
50 }
51 ...
52 }
上面代码主要就是将每个address:port对应的所有域名与域名所在的虚拟主机配置信息建立hash映射,这样就可以通过域名快速找到域名所在的虚拟主机配置信息。有关nginx的hash可以参考 nginx源码分析之hash的实现 这篇文章。下面再看下ngx_http_init_listening函数:
1 static ngx_int_t
2 ngx_http_init_listening(ngx_conf_t *cf, ngx_http_conf_port_t *port)
3 {
4 ...
5 addr = port->addrs.elts;
6 last = port->addrs.nelts;
7 if (addr[last - 1].opt.wildcard) {
8 addr[last - 1].opt.bind = 1;
9 bind_wildcard = 1;
10
11 } else {
12 bind_wildcard = 0;
13 }
14 i = 0;
15 while (i < last) { //last代表的是address:port的个数
16 //忽略隐式绑定
17 if (bind_wildcard && !addr[i].opt.bind) {
18 i++;
19 continue;
20 }
21 //这个函数里面将会创建,并且初始化listen结构,这个listen已经存放在cycle结构的listen数组中
22 ls = ngx_http_add_listening(cf, &addr[i]);
23 if (ls == NULL) {
24 return NGX_ERROR;
25 }
26 hport = ngx_pcalloc(cf->pool, sizeof(ngx_http_port_t));
27 ...
28 ls->servers = hport;
29 if (i == last - 1) { //将*:port和没有显式bind的address:port放在同一个listen中
30 hport->naddrs = last;
31 } else {
32 hport->naddrs = 1;
33 i = 0; //i重新赋值为0
34 }
35 switch (ls->sockaddr->sa_family) {
36 ...
37 default: /* AF_INET */
38 if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) { //初始化虚拟主机相关的地址,设置hash等等.
39 return NGX_ERROR;
40 }
41 break;
42 }
43 addr++;
44 last--;
45 }
46 return NGX_OK;
47 }
这个函数就是遍历某个端口port对应的所有address,如果所有address中不包含通配符,则对所有的address:port调用ngx_http_add_listening分配一个listen结构和ngx_http_port_t结构,并初始化它们。如果存在address包含通配符,则如果address:port需要bind,分配一个listen结构和ngx_http_port_t结构,并初始化它们,对所有address:port不需要bind的,它们和包含通配符*:port共同使用一个listen结构和ngx_http_port_t结构,并且listen结构中包含的地址是*:port,所以最好bind的地址是*:port。所有的listen都会存放在全局变量ngx_cycle的listening数组中,这样后面就可以利用这些address:port信息建立每个套接字了。
建立监听套接字
建立监听套接字是在ngx_open_listening_sockets中完成,这个函数是在ngx_init_cycle中被调用的。
1 ngx_int_t 2 ngx_open_listening_sockets(ngx_cycle_t *cycle) 3 { 4 ... 5 reuseaddr = 1; 6 ... 7 log = cycle->log; 8 //尝试5次 9 for (tries = 5; tries; tries--) { 10 failed = 0; 11 ls = cycle->listening.elts; 12 for (i = 0; i < cycle->listening.nelts; i++) { 13 ... 14 //创建socket 15 s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0); 16 ... //设置socket 17 //绑定socket 18 if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) { 19 ... 20 } 21 ... 22 //设置socket为监听套接字 23 if (listen(s, ls[i].backlog) == -1) { 24 ... 25 } 26 ls[i].listen = 1; 27 ls[i].fd = s; 28 } 29 30 if (!failed) { 31 break; 32 } 33 } 34 ... 35 }
这个函数就是遍历listening数组,为每个listen结构创建监听套接字。到目前为止,所有的网络初始化部分就基本完成了,然后就是根据这些监听套接字来获取客户端的连接请求,并处理这些请求了。怎样获取客户端连接和nginx的进程模型和事件处理有关,进程模型和事件处理后面再贴文章分析。
nginx源码分析之网络初始化相关推荐
- nginx源码分析之模块初始化
在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...
- nginx源码分析—模块及其初始化
Content 0. 序 1. nginx有哪些模块? 2. nginx如何描述这些模块? 2.1 模块数据结构 2.1.1 ngx_module_t结构 2.1.2 ngx_command_t结构 ...
- nginx源码分析(5)——监听socket初始化
在nginx源码分析(4)中,看到了nginx的事件模型,但其中没有介绍监听socket的初始化.而对于web server来说,需要通过监听socket来监听客户端的连接等.本篇将会具体介绍这方面的 ...
- nginx源码分析之内存池与线程池丨nginx的多进程网络实现
nginx源码分析之内存池与线程池 1. nginx的使用场景 2. nginx源码 内存池,线程池,日志 3. nginx的多进程网络实现 视频讲解如下,点击观看: [Linux后台开发系统]ngi ...
- Nginx源码分析:epoll事件处理模块概述
nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> 事件处理模块概述 Nginx的高效请求的处理依赖于事件管理机制,本次默认的场景是Linux操 ...
- Nginx源码分析:惊群处理与负载均衡
nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> Nginx的惊群处理与负载均衡概述 当Nginx工作在master/worker模式下时,就 ...
- Nginx源码分析:核心数据结构ngx_cycle_t与内存池概述
nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> 核心数据结构与内存池概述 在Nginx中的核心数据结构就是ngx_cycle_t结构,在初始 ...
- Nginx源码分析:master/worker工作流程概述
nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> Nginx的master与worker工作模式 在生成环境中的Nginx启动模式基本都是以m ...
- Nginx源码分析:启动流程
nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> nginx简介 Nginx的作为服务端软件,表现的主要特点是更快.高扩展.高可靠性.低内存消 ...
最新文章
- nodejs-模块系统
- 用Windows API实现多线程--原理例子
- web command line : http://yubnub.org/
- MySQL深度剖析之索引专题(2021)
- 【渝粤教育】电大中专电子商务网站建设与维护 (3)作业 题库
- 安卓快速排序与冒泡排序
- Hexo Next底部powered by的logo栏更改以及注意事项(附官方文档,文末有福利链)
- java敏感词汇过滤工具类
- linux以及网络协议
- 解决Tortoise git没有红色、绿色勾勾的问题
- python中的error:excepted an indented block
- Ansys 2022 安装教程(附赠免费的安装包)
- F - 喜欢砍竹子的黑泽明
- Android蓝牙开发与串口蓝牙通讯
- 如何创建自己的社区平台_建立自己的平台
- try 、catch、finally用法总结
- 【CST】-01软件安装及熟悉
- codeforces 574B 暴力+复杂度分析
- SCIENCE CHINA LATEX模板缺少的一个重要文件psfig.sty,File `picins.sty’ not found.
- 身份证OCR识别是什么?
热门文章
- ORACLE 内置函数之 GREATEST 和 LEAST(转)
- 洛谷 - P2598 [ZJOI2009]狼和羊的故事(最大流最小割)
- 牛客 - 二分(差分)
- CodeForces - 570D Tree Requests(树上启发式合并)
- 洛谷 - P1886 滑动窗口(单调队列/线段树)
- python入门与实践在线阅读_Python编程:从入门到实践(第2版)
- 案例逐步演示python利用正则表达式提取指定内容并输出到csv
- 主定理(master theorem)学习小记
- 基于 ida 的反汇编转换 Obj 的可行性 笔记(2)
- SurfaceView 和 GLSurfaceView