• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    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字段中。

    总结

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

    上一篇:nginx配置ssl实现https访问的步骤(适合新手)
    下一篇:Nginx中accept锁的机制与实现详解
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯

    时间:9:00-21:00 (节假日不休)

    地址:江苏信息产业基地11号楼四层

    《增值电信业务经营许可证》 苏B2-20120278

    nginx中的listen指令实例解析 nginx,中的,listen,指令,实例,