内容简介:微信公众号:关注可了解更多的关注公众号,有趣有内涵的文章第一时间送达!本篇文章详细介绍一下
微信公众号:关注可了解更多的 Nginx
知识。任何问题或建议,请公众号留言;
关注公众号,有趣有内涵的文章第一时间送达!
前言
本篇文章详细介绍一下 listen
指令的解析,以及 socket
的创建过程。
listen
的内容太多了,并且牵涉到后面的很多地方,所以只能再一次的回到这里仔细的学习 listen
指令的解析过程。首先要参考 listen
的 nginx
官方文档 ,知道listen的用法,然后才能学习源码。
listen配置
我们先从源文件中找到 listen
的配置项,如下:
1{ 2 ngx_string("listen"), 3 NGX_HTTP_SRV_CONF|NGX_CONF_1MORE, 4 ngx_http_core_listen, 5 NGX_HTTP_SRV_CONF_OFFSET, 6 0, 7 NULL 8} 复制代码
我们从配置文件中可以看到, listen
指令的解析函数为 ngx_http_core_listen
,我们下面分析一下这个函数。
使用到的结构体
1typedef struct { 2 union { 3 struct sockaddr sockaddr; 4 struct sockaddr_in sockaddr_in; 5#if (NGX_HAVE_INET6) 6 struct sockaddr_in6 sockaddr_in6; 7#endif 8#if (NGX_HAVE_UNIX_DOMAIN) 9 struct sockaddr_un sockaddr_un; 10#endif 11 u_char sockaddr_data[NGX_SOCKADDRLEN]; 12 } u; 13 14 socklen_t socklen; 15 16 unsigned set:1; 17 unsigned default_server:1; 18 unsigned bind:1; 19 unsigned wildcard:1; 20#if (NGX_HTTP_SSL) 21 unsigned ssl:1; 22#endif 23#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) 24 unsigned ipv6only:2; 25#endif 26 27 int backlog; 28 int rcvbuf; 29 int sndbuf; 30#if (NGX_HAVE_SETFIB) 31 int setfib; 32#endif 33 34#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) 35 char *accept_filter; 36#endif 37#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) 38 ngx_uint_t deferred_accept; 39#endif 40 41 u_char addr[NGX_SOCKADDR_STRLEN + 1]; 42} ngx_http_listen_opt_t; 复制代码
上面的几个数据结构是我们分析 listen
指令时常用到的,这里简单的说明一下他们的作用。
-
ngx_url_t
结构体是用来保存解析address:port
的内容。 -
ngx_http_listen_opt_t
结构体是用来保存listen
指令后面所配置的选项。为后面创建socket
做准备。
解析地址
我们阅读 ngx_http_core_listen
的源码就会发现,解析 listen
指令的第一步是调用 ngx_parse_url()
来解析 address:port
部分。这其实是 ngx_http_core_listen
函数最重要的一部分,我们先来分析一下这个函数。
首先,我们必须要知道 listen
指令配置的 address:port
的格式,我们这里只分析 ipv4
格式,我们从 nginx
文档中可以查到:
Sets the address and port for IP, or the path for a UNIX-domain socket on which the server will accept requests. Both address and port, or only address or only port can be specified. An address may also be a hostname, for example: listen 127.0.0.1:8000; listen 127.0.0.1; listen 8000; listen *:8000; listen localhost:8000; If only address is given, the port 80 is used. If the directive is not present then either *:80 is used if nginx runs with the superuser privileges, or *:8000 otherwise.
通过上面的文档我们可以知道, address:port
可以有一下几种格式:
1listen 8080 2listen *:8080 3listen 127.0.0.1:8080 4listen localhost:8080 5listen 127.0.0.1; 复制代码
如果我们没有指定 port
,那么默认是 80
端口。
如果我们在 server
中没有配置 listen
指令,那么会有两种情况:
- 当前启动
nginx
的是普通用户,那么默认为*:80
- 当前启动
nginx
的是root
用户,那么默认为*:8000
那么 nginx
是如何解析 address:port
的呢?这就是下面的 ngx_parse_url()
函数的功能了。
1 u.url = value[1]; 2 u.listen = 1; 3 u.default_port = 80; 4 5 if (ngx_parse_url(cf->pool, &u) != NGX_OK) { 6 if (u.err) { 7 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 8 "%s in \"%V\" of the \"listen\" directive", 9 u.err, &u.url); 10 } 11 return NGX_CONF_ERROR; 12 } 复制代码
在调用 ngx_parse_url()
函数之前,会先进行一些简单的赋值,
1 // address:port部分 2 u.url = value[1]; 3 // 表示当前server显式的设置了listen指令 4 u.listen = 1; 5 // 设置一个默认的端口号 6 u.default_port = 80; 复制代码
由于我们使用的是 ipv4
地址,所以最终调用的是 ngx_parse_inet_url()
,如下:
1static ngx_int_t 2ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u) 3{ 4 u_char *p, *host, *port, *last, *uri, *args; 5 size_t len; 6 ngx_int_t n; 7 struct hostent *h; 8 struct sockaddr_in *sin; 9 10 u->socklen = sizeof(struct sockaddr_in); 11 sin = (struct sockaddr_in *) &u->sockaddr; 12 sin->sin_family = AF_INET; 13 u->family = AF_INET; 14 host = u->url.data; 15 last = host + u->url.len; 16 17 port = ngx_strlchr(host, last, ':');// 判断是否有端口号 18 uri = ngx_strlchr(host, last, '/');// 判断是否有path 19 args = ngx_strlchr(host, last, '?');// 判断是否有QueryString 20 21 if (args) { 22 /*我们的listen指令后面的address:port没有args,不会执行这里*/ 23 } 24 if (uri) { 25 /*我们的listen指令后面的address:port没有uri,不会执行这里*/ 26 } 27 if (port) {//如果有端口号 28 port++;//port向后移动一位,表示从此位置开始是端口号 29 len = last - port;//端口号的长度 30 n = ngx_atoi(port, len);// 把端口号转换为数字 31 u->port = (in_port_t) n; 32 sin->sin_port = htons((in_port_t) n); 33 u->port_text.len = len; 34 u->port_text.data = port; 35 last = port - 1;// 此时last指向了address的最后 36 } else { 37 // 如果我们没有冒号,这时候有两种情况, 38// ① 我们没有指定端口号,如 listen 127.0.0.1 39// ② 我们指定了端口号,但是没有指定address,如 listen 8080 40 if (uri == NULL) { 41 // 我们在server中显式的使用了listen指令 42 if (u->listen) { 43 /* test value as port only */ 44 // 这句话注释的很明显,nginx首先将它作为一个port 45 // 进行转换,如果成功,那么就认为这是一个port 46 n = ngx_atoi(host, last - host); 47 if (n != NGX_ERROR) { 48//对于上面的第①种情况,由于无法将 127.0.0.1 49// 转换为一个正确的端口号, 50// 所以就不会执行下面的if语句,而是执行 51// u->noport = 1 , 表示我们没有指定端口号 52 u->port = (in_port_t) n; 53 sin->sin_port = htons((in_port_t) n); 54 u->port_text.len = last - host; 55 u->port_text.data = host; 56 u->wildcard = 1; 57 return NGX_OK; 58 } 59 } 60 } 61 // 对于上述的第①种情况,会执行到这里,表示我们没有指定端口号 62 u->no_port = 1; 63 } 64// 如果执行到这里,说明listen后面没有端口号,只有address 65// len 表示address的长度, 66// 比如 127.0.0.1 或者 localhost的长度 67 len = last - host; 68 if (len == 0) { 69 u->err = "no host"; 70 return NGX_ERROR; 71 } 72 if (len == 1 && *host == '*') { 73 len = 0; 74 } 75 76 u->host.len = len; 77 u->host.data = host; 78 if (u->no_resolve) { 79 return NGX_OK; 80 } 81 82 if (len) { 83 // 对于 listen * 的情况,上面的代码会把len设置为0,所以不会执行这里 84// 这里会首先尝试把address转换为ip形式,如果转换不成功, 85// 那么就会调用gethostbyname()进行DNS地址解析 86// 比如 127.0.0.1这种形式就可以通过 ngx_inet_addr()进行转换, 87// 这时就不会调用gethostbyname()进行DNS解析 88// 但是对于 localhost 这种情况,只能进行DNS地址解析 89 sin->sin_addr.s_addr = ngx_inet_addr(host, len); 90 91 if (sin->sin_addr.s_addr == INADDR_NONE) { 92 p = ngx_alloc(++len, pool->log); 93 (void) ngx_cpystrn(p, host, len); 94 95 h = gethostbyname((const char *) p); 96 97 ngx_free(p); 98 99 if (h == NULL || h->h_addr_list[0] == NULL) { 100 u->err = "host not found"; 101 return NGX_ERROR; 102 } 103 104 sin->sin_addr.s_addr = *(in_addr_t *) (h->h_addr_list[0]); 105 } 106 107 if (sin->sin_addr.s_addr == INADDR_ANY) { 108 u->wildcard = 1; 109 } 110 111 } else {// address和port都忽略的时候 112 sin->sin_addr.s_addr = INADDR_ANY; 113 u->wildcard = 1; 114 } 115 116 if (u->no_port) { 117 // 如果没有指定端口号,那么会使用默认的80端口 118// 从这里也可以看出来,ngx_url_t 的 default_port 字段就是用来保存默认端口的 119// 如果我们没有指定一个明确的端口号,那么就会使用这个默认的端口,默认是 80 120 u->port = u->default_port; 121 sin->sin_port = htons(u->default_port); 122 } 123 124 if (u->listen) { 125 return NGX_OK; 126 } 127//因为我们的先决条件是 u->listen = 1,所以下面的语句不会被执行 128 if (ngx_inet_resolve_host(pool, u) != NGX_OK) { 129 return NGX_ERROR; 130 } 131 132 return NGX_OK; 133} 复制代码
结合图片以及代码中的注释,我们简单的分析一下nginx对listen指令中 address:port
的解析方法:
address:port
的格式组合格式有好几种
address
: 可以没有该字段,可以为IP地址,可以为域名
port
: 可以不设置该字段,可以为一个固定的端口
所以组合形式就有 3 * 2 = 6
中。
nginx
对于他们的解析有固定的格式,如下:
如果 address
没有设置,那么 u->wildcard = 1
,表示这时一个通配的地址匹配
如果 address
设置为一个ip格式,那么监听的地址就是这个ip地址
如果 address
是一个域名格式,那么就会对该域名进行DNS地址解析,获取监听的IP地址
如果端口号为空,那么就会使用默认的80端口。
address:port
解析完之后,我们可以从 listen
指令的处理函数 ngx_http_core_listen()
中看到,只有 u.sockaddr
, u.socklen
,以及 u.wildcard
三个字段被 ngx_http_listen_opt_t
结构体用到了, ngx_url_t
的其他字段都是作为 ngx_parse_url()
函数的辅助字段使用。我们在后续的分析过程中,可以结合上面的图片进行学习
内部布局总结
根据上面的分析,我们对常见的几种 address:port
格式的内存布局进行了总结,如下:
1listen 8080 2listen *:8080 3listen 127.0.0.1:8080 4listen localhost:8080 复制代码
对应的图片如下:
喜欢本文的朋友们,欢迎长按下图关注订阅号郑尔多斯,更多精彩内容第一时间送达
以上所述就是小编给大家介绍的《listen源码分析第一篇 address:port分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
大学程序设计课程与竞赛训练教材
吴永辉、王建德 / 机械工业出版社 / 2013-6 / 69.00
本书每章为一个主题,实验内容安排紧扣大学算法和数学的教学,用程序设计竞赛中的算法和数学试题作为实验试题,将算法和数学的教学与程序设计竞赛的解题训练结合在一起;在思维方式和解题策略的训练方面,以问题驱动和启发式引导为主要方式,培养读者通过编程解决问题的能力。 本书特点: 书中给出的234道试题全部精选自ACM国际大学生程序设计竞赛的世界总决赛以及各大洲赛区现场赛和网络预赛、大学程序设计竞......一起来看看 《大学程序设计课程与竞赛训练教材》 这本书的介绍吧!