首页 > 文章列表 > nginx中的listen指令怎么用

nginx中的listen指令怎么用

nginx listen
459 2023-05-14

nginx中的listen指令怎么用

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字段中。