nginx中的listen指令实例解析

剧情回顾

上一篇文章我们分析了location指令的解析过程,简单的回顾一下这个内容:每个location对应一个ngx_http_core_loc_conf_t结构体,所有的location通过一个双向队列连接在一起。数据结构比较复杂。

listen指令

nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解。与网络有关的配置命令主要有两个:listen和sever_name。listen命令设置nginx监听地址,对于IP协议,这个地址就是address和port,对于UNIX域套接字协议,这个地址就是path,一条listen指令只能指定一个address或者port,address也可以是主机名

从这一篇文章开始,我们分析listen指令的解析过程,listen指令的配置如下:从nginx.org的手册中我们可以获取listen的使用方法:

listen address[:port] [default_server] [setfib=number] [backlog=number] [rcvbuf=size] [sndbuf=size] [accept_filter=filter] [deferred] [bind] [ipv6only=on|off] [ssl] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];

一个listen指令携带的参数是很复杂的。不过,我们一般很少关注那些不太常用的参数,以下是一些常用的配置方式:

listen 127.0.0.1:8000;
listen 127.0.0.1 不加端口,默认监听80端口;
listen 8000
listen *:8000
listen localhost:8000

解析listen指令中的uri和端口

从上面的内容知道,listen有多种用法,我们在解析的时候需要获取到listen指令的端口号和uri部分,nginx提供了ngx_parse_url()方法来解析uri和port,该函数在解析listen指令的时候会被调用。

ngx_int_t
ngx_parse_url(ngx_pool_t *pool, ngx_url_t *u)
{
 u_char *p;
 size_t len;

 p = u->url.data;
 len = u->url.len;
 // 这里是解析unix domain的协议
 if (len >= 5 && ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0) {
 return ngx_parse_unix_domain_url(pool, u);
 }
 // 解析IPV6协议
 if (len && p[0] == '[') {
 return ngx_parse_inet6_url(pool, u);
 }
 // 解析IPV4协议
 return ngx_parse_inet_url(pool, u);
}

我们使用的是IPV4协议,这里分析ngx_parse_inet_url()函数

// u.url = "80";
// u.listen = 1;
// u.default_port = 80;
static ngx_int_t
ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u)
{
 u_char *p, *host, *port, *last, *uri, *args;
 size_t len;
 ngx_int_t n;
 struct sockaddr_in *sin;
#if (NGX_HAVE_INET6)
 struct sockaddr_in6 *sin6;
#endif

 u->socklen = sizeof(struct sockaddr_in);
 sin = (struct sockaddr_in *) &u->sockaddr;
 sin->sin_family = AF_INET;// IPV4类型

 u->family = AF_INET; 

 host = u->url.data; // "80"

 last = host + u->url.len; // host的最后字符的位置

 port = ngx_strlchr(host, last, ':'); // 找到port, 这里为 NULL

 uri = ngx_strlchr(host, last, '/'); // 找到uri,这里为 NULL

 args = ngx_strlchr(host, last, '?'); // 找到参数args,这里为 NULL

 if (args) {
 if (uri == NULL || args < uri) {
 uri = args;
 }
 }

 if (uri) {
 if (u->listen || !u->uri_part) {
 u->err = "invalid host";
 return NGX_ERROR;
 }

 u->uri.len = last - uri;
 u->uri.data = uri;

 last = uri;

 if (uri < port) {
 port = NULL;
 }
 }

 if (port) {
 port++;

 len = last - port;

 n = ngx_atoi(port, len);

 if (n < 1 || n > 65535) {
 u->err = "invalid port";
 return NGX_ERROR;
 }

 u->port = (in_port_t) n;
 sin->sin_port = htons((in_port_t) n);

 u->port_text.len = len;
 u->port_text.data = port;

 last = port - 1;

 } else {
 if (uri == NULL) {

 if (u->listen) {

 /* test value as port only */

 n = ngx_atoi(host, last - host);

 if (n != NGX_ERROR) {

 if (n < 1 || n > 65535) {
 u->err = "invalid port";
 return NGX_ERROR;
 }

 u->port = (in_port_t) n;
 sin->sin_port = htons((in_port_t) n);

 u->port_text.len = last - host;
 u->port_text.data = host;

 u->wildcard = 1;

 return NGX_OK;
 }
 }
 }

 u->no_port = 1;
 u->port = u->default_port;
 sin->sin_port = htons(u->default_port);
 }

 len = last - host;

 if (len == 0) {
 u->err = "no host";
 return NGX_ERROR;
 }

 u->host.len = len;
 u->host.data = host;

 if (u->listen && len == 1 && *host == '*') {
 sin->sin_addr.s_addr = INADDR_ANY;
 u->wildcard = 1;
 return NGX_OK;
 }

 sin->sin_addr.s_addr = ngx_inet_addr(host, len);

 if (sin->sin_addr.s_addr != INADDR_NONE) {

 if (sin->sin_addr.s_addr == INADDR_ANY) {
 u->wildcard = 1;
 }

 u->naddrs = 1;

 u->addrs = ngx_pcalloc(pool, sizeof(ngx_addr_t));
 if (u->addrs == NULL) {
 return NGX_ERROR;
 }

 sin = ngx_pcalloc(pool, sizeof(struct sockaddr_in));
 if (sin == NULL) {
 return NGX_ERROR;
 }

 ngx_memcpy(sin, &u->sockaddr, sizeof(struct sockaddr_in));

 u->addrs[0].sockaddr = (struct sockaddr *) sin;
 u->addrs[0].socklen = sizeof(struct sockaddr_in);

 p = ngx_pnalloc(pool, u->host.len + sizeof(":65535") - 1);
 if (p == NULL) {
 return NGX_ERROR;
 }

 u->addrs[0].name.len = ngx_sprintf(p, "%V:%d",
  &u->host, u->port) - p;
 u->addrs[0].name.data = p;

 return NGX_OK;
 }

 if (u->no_resolve) {
 return NGX_OK;
 }

 if (ngx_inet_resolve_host(pool, u) != NGX_OK) {
 return NGX_ERROR;
 }

 u->family = u->addrs[0].sockaddr->sa_family;
 u->socklen = u->addrs[0].socklen;
 ngx_memcpy(&u->sockaddr, u->addrs[0].sockaddr, u->addrs[0].socklen);

 switch (u->family) {

#if (NGX_HAVE_INET6)
 case AF_INET6:
 sin6 = (struct sockaddr_in6 *) &u->sockaddr;

 if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
 u->wildcard = 1;
 }

 break;
#endif

 default: /* AF_INET */
 sin = (struct sockaddr_in *) &u->sockaddr;

 if (sin->sin_addr.s_addr == INADDR_ANY) {
 u->wildcard = 1;
 }

 break;
 }

 return NGX_OK;
}

这个函数就是解析了我们listen的地址和端口号,我们的配置文件中,端口号为80,并没有配置监听地址,所以u->wildcard = 1,表示这是一个通配符,要监听该服务器所有ip地址的这个端口号。

解析listen指令

下面从源码中看一下listen的配置:

{
 ngx_string("listen"),
 NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
 ngx_http_core_listen,
 NGX_HTTP_SRV_CONF_OFFSET,
 0,
 NULL
}

从配置文件中我们可以知道,listen只能出现在server 模块中,可以带有多个参数。

对应的处理函数为 ngx_http_core_listen,下面我们分析这个函数,我们删除了一些进行错误判断的代码,

static char *
ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
 ngx_http_core_srv_conf_t *cscf = conf;

 ngx_str_t *value, size;
 ngx_url_t u;
 ngx_uint_t n;
 ngx_http_listen_opt_t lsopt;

 cscf->listen = 1;

 value = cf->args->elts;

 ngx_memzero(&u, sizeof(ngx_url_t));

 u.url = value[1];
 u.listen = 1;
 u.default_port = 80;

 if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
 return NGX_CONF_ERROR;
 }

 ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));

 ngx_memcpy(&lsopt.sockaddr.sockaddr, &u.sockaddr, u.socklen);

 lsopt.socklen = u.socklen;
 lsopt.backlog = NGX_LISTEN_BACKLOG;
 lsopt.rcvbuf = -1;
 lsopt.sndbuf = -1;
#if (NGX_HAVE_SETFIB)
 lsopt.setfib = -1;
#endif
#if (NGX_HAVE_TCP_FASTOPEN)
 lsopt.fastopen = -1;
#endif
 lsopt.wildcard = u.wildcard;
#if (NGX_HAVE_INET6)
 lsopt.ipv6only = 1;
#endif

 (void) ngx_sock_ntop(&lsopt.sockaddr.sockaddr, lsopt.socklen, lsopt.addr,
  NGX_SOCKADDR_STRLEN, 1);

 for (n = 2; n < cf->args->nelts; n++) {

 if (ngx_strcmp(value[n].data, "default_server") == 0
 || ngx_strcmp(value[n].data, "default") == 0)
 {
 lsopt.default_server = 1;
 continue;
 }
 // 这里面的其他代码都是处理listen的各种参数,对我们这里的分析没有用处
 }

 if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) {
 return NGX_CONF_OK;
 }

 return NGX_CONF_ERROR;
}

这个函数的整体流程就是解析listen指令的各个参数,生成一个 ngx_http_listen_opt_t,顾名思义,这个结构体就是保存一些监听端口的选项(listening port option)。这里调用了一个函数ngx_parse_url(),我们上面已经分析过了,这个函数的作用就是解析url中的address和port。

然后最重要的部分就要到了,ngx_http_core_listen()函数在最后面调用了ngx_http_add_listen()函数,该函数是将listen的端口信息保存到ngx_http_core_main_conf_t结构体的ports动态数组中。

ngx_http_add_listen()函数

// cf: 配置结构体
// cscf: listen指令所在的server的配置结构体
// lsopt : ngx_http_core_listen()生成的listen option
ngx_int_t
ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
 ngx_http_listen_opt_t *lsopt)
{
 in_port_t     p;
 ngx_uint_t     i;
 struct sockaddr   *sa;
 ngx_http_conf_port_t  *port;
 ngx_http_core_main_conf_t *cmcf;
 // 获取 ngx_http_core_module模块的main_conf结构体ngx_http_core_main_conf_t
 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
 // ports字段是一个数组
 if (cmcf->ports == NULL) {
  cmcf->ports = ngx_array_create(cf->temp_pool, 2,
          sizeof(ngx_http_conf_port_t));
  if (cmcf->ports == NULL) {
   return NGX_ERROR;
  }
 }

 sa = &lsopt->sockaddr.sockaddr;
 p = ngx_inet_get_port(sa);

 port = cmcf->ports->elts;
 for (i = 0; i < cmcf->ports->nelts; i++) {

  if (p != port[i].port || sa->sa_family != port[i].family) {
   continue;
  }

  /* a port is already in the port list */

  return ngx_http_add_addresses(cf, cscf, &port[i], lsopt);
 }

 /* add a port to the port list */

 port = ngx_array_push(cmcf->ports);
 if (port == NULL) {
  return NGX_ERROR;
 }

 port->family = sa->sa_family;
 port->port = p;
 port->addrs.elts = NULL;

 return ngx_http_add_address(cf, cscf, port, lsopt);
}

这个函数将端口号的信息保存到了 ngx_http_core_main_conf_t结构体的port字段中。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Nginx配置指令location匹配符优先级和安全问题

    最近一直在做location 配置,遇到优先级别问题(如果配置不当可能存在安全隐患哦),以下是个人学习一点体会. 一. location 的匹配符1.等于匹配符:=等于匹配符就是等号,特点可以概括为两点:精确匹配不支持正则表达式2.空匹配符空匹配符的特点是:匹配以指定模式开始的 URI不支持正则表达式3.正则匹配符:~正则匹配符是可以使用正则表达式的匹配符.不过这里要强调的是,一般来说~是指:区分大小写的正则匹配而~*表示:不区分大小写的正则匹配但是对于一些对大小写不敏感的操作系统,这两者没有区

  • Nginx配置中指令root和alias的区别浅析

    前言 最近在nginx上部署日志分析工具awstats时,在配置awstats分析结果可供网页浏览这步时,分析结果页面访问总是404.后来查阅了一些资料,发现是root和alias的用法区别没搞懂导致的,这里特地将这两者区别详尽道来,供大家学习参考. root和alias都可以定义在location模块中,都是用来指定请求资源的真实路径,比如: location /i/ { root /data/w3; } 请求 http://foofish.net/i/top.gif 这个地址时,那么在服务器

  • nginx add_header指令使用方法

    response header一般都是以key:value的形式,例如:"Content-Encoding:gzip.Cache-Control:no-store",设置的命令为: 复制代码 代码如下: add_header Cache-Control no-storeadd_header Content-Encoding gzip 但是有一个十分常用的response header比较特性,就是Content-Type,可以在它设置了类型的同时还会指定charset,例如:"

  • nginx try_files指令判断文件是否存在实例

    现在有这样一个需求,网站根目录下有静态文件,static目录下也有静态文件,static目录下的静态文件是程序批量生成的,我想让nginx在地址不变的前提下优先使用static目录里面的文件,如果不存在再使用根目录下的静态文件,比如访问首页http://example.com/index.html则nginx返回/static/index.html,如果不存在返回/index.html. 经过一番研究可以用if指令实现,关键配置如下,这条配置需要放到靠前的位置 复制代码 代码如下: if (-e

  • 利用Nginx的map指令实现页面跳转

    前言 本文主要介绍了利用Nginx中map指令实现页面跳转的方法,有需要的下面来一起学习学习. 实现方法 在 nginx.conf 的 http 域中添加 map 指令规则,这里包含一个 URL 规则映射文件: # 设置301跳转 map $request_uri $new { include /etc/nginx/redirect.map; } map 指令支持引入一个映射文件的方式,这里 redirect.map 文件内容格式如下: ~^/page/mordern-php$ /page/mo

  • Nginx SSI指令配置详解

    什么是SSI Server Side Include,通常称为服务器端嵌入,是一种类似于ASP的基于服务器的网页制作技术.大多数(尤其是基于Unix平台)的WEB服务器如Netscape Enterprise Server等均支持SSI命令. 为什么要用SSI 用个例子来说明,一个静态化的页面中,需要嵌入一小块实时变化的内容,.例如首页,大部分的页面内容需要缓存但是用户登录后的个人信息是动态信息,不能缓存.那么如何解决这个"页面部分缓存"问题,利用SSI就可以解决,在首页的静态页面中嵌

  • nginx HTTP模块配置常用指令

    一.HTTP模块的作用是什么? Nginx的HTTP模块用于控制Nginx的HTTP进程. 二.配置指令 1. alias含义:指定location使用的路径,与root类似,但不改变文件的跟路径,仅适用文件系统的路径.语法:alias <file-path | directory-path>缺省:N/A作用域:http.server.location示例: 复制代码 代码如下: location /i/ {    alias /home/michael/web/i/;} 如请求 /i/log

  • Nginx Gzip模块启用和配置指令详解

    Nginx的gzip模块是内置的,在http中添加如下配置: 复制代码 代码如下: gzip on;gzip_min_length  5k;gzip_buffers     4 16k;gzip_http_version 1.0;gzip_comp_level 3;gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php

  • nginx proxy_pass指令’/’使用注意事项

    1. proxy_pass配置说明 不带/ 复制代码 代码如下: location /test/ { proxy_pass http://t6:8300;  } 带/ 复制代码 代码如下: location /test/  {                  proxy_pass http://t6:8300/;   } 上面两种配置,区别只在于proxy_pass转发的路径后是否带 "/" 针对情况1,如果访问url = http://server/test/test.jsp,则被

  • nginx日志配置指令详解

    日志对于统计排错来说非常有利的.本文总结了nginx日志相关的配置如access_log.log_format.open_log_file_cache.log_not_found.log_subrequest.rewrite_log.error_log. nginx有一个非常灵活的日志记录模式.每个级别的配置可以有各自独立的访问日志.日志格式通过log_format命令来定义.ngx_http_log_module是用来定义请求日志格式的. 1. access_log指令 语法: access_

随机推荐