微信公众号:郑尔多斯
关注可了解更多的Nginx
知识。任何问题或建议,请公众号留言;
关注公众号,有趣有内涵的文章第一时间送达!
初始化监听端口
前言
上文介绍了ngx_http_optimize_servers()
函数的一部分内容,下面继续介绍剩下的重头戏。
初始化端口
for (p = 0; p < ports->nelts; p++) {
// 前面的内容已经介绍完了
if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
return NGX_ERROR;
}
}
源码分析
下面我们详细介绍ngx_http_init_listening()
函数,源码如下:
static ngx_int_t
ngx_http_init_listening(ngx_conf_t *cf, ngx_http_conf_port_t *port)
{
ngx_uint_t i, last, bind_wildcard;
ngx_listening_t *ls;
ngx_http_port_t *hport;
ngx_http_conf_addr_t *addr;
addr = port->addrs.elts;
last = port->addrs.nelts;
/*
* If there is a binding to an "*:port" then we need to bind() to
* the "*:port" only and ignore other implicit bindings. The bindings
* have been already sorted: explicit bindings are on the start, then
* implicit bindings go, and wildcard binding is in the end.
*/
// addr 是排过序的,放在最前面的是需要bind的
// addr 数组最后一个元素是宽绑定。即:*:port
// 就是监听最前面的元素的端口地址和最后一个元素的端口。
if (addr[last - 1].opt.wildcard) {
addr[last - 1].opt.bind = 1;
bind_wildcard = 1;
} else {
bind_wildcard = 0;
}
i = 0;
while (i < last) {
if (bind_wildcard && !addr[i].opt.bind) {
// 仔细分析一下,i的值就是那些没有显式的指定bind,将要被包含在wildcard中addr的数量
// 因为排序之后的bind在最前面,所以当出现addr[i].opt.bind=0开始,那么后面的addr.opt.bind都为0,
// 因为这是排序之后的数组。最后的一个元素的addr.opt.bind被nginx设置为了1,参考上面的代码
i++;
continue;
}
// 如果能执行到这里,那么有两个条件
// ① bind_wildcard=0,即不存在类似 listen *:80 的这种wildcard情况
// ② bind_wildcard = 1, 即存在wildcard,但是当前的listen指令显式的指定了bind属性
ls = ngx_http_add_listening(cf, &addr[i]);
hport = ngx_pcalloc(cf->pool, sizeof(ngx_http_port_t));
ls->servers = hport;
if (i == last - 1) {
hport->naddrs = last;
} else {
hport->naddrs = 1;
i = 0;
}
switch (ls->sockaddr->sa_family) {
default: /* AF_INET */
if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) {
return NGX_ERROR;
}
break;
}
addr++;
last--;
}
return NGX_OK;
}
监听端口
上面的源码中有一个非常重要的函数ngx_http_add_listening()
,这个函数实现了对端口的监听。
static ngx_listening_t *
ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
{
ngx_listening_t *ls;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t *cscf;
// 创建一个新的ngx_listening_t结构体,表示监听的端口
ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);
ls->addr_ntop = 1;
//下面的一行代码很重要。在accept成功之后会调用ls->handler函数
ls->handler = ngx_http_init_connection;
cscf = addr->default_server;
ls->pool_size = cscf->connection_pool_size;
ls->post_accept_timeout = cscf->client_header_timeout;
clcf = cscf->ctx->loc_conf[ngx_http_core_module.ctx_index];
ls->logp = clcf->error_log;
ls->log.data = &ls->addr_text;
ls->log.handler = ngx_accept_log_error;
ls->backlog = addr->opt.backlog;
ls->rcvbuf = addr->opt.rcvbuf;
ls->sndbuf = addr->opt.sndbuf;
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
ls->accept_filter = addr->opt.accept_filter;
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
ls->deferred_accept = addr->opt.deferred_accept;
#endif
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
ls->ipv6only = addr->opt.ipv6only;
#endif
#if (NGX_HAVE_SETFIB)
ls->setfib = addr->opt.setfib;
#endif
return ls;
}
上面的函数又调用了ngx_create_listening()
,源码如下:
ngx_listening_t *
ngx_create_listening(ngx_conf_t *cf, void *sockaddr, socklen_t socklen)
{
size_t len;
ngx_listening_t *ls;
struct sockaddr *sa;
u_char text[NGX_SOCKADDR_STRLEN];
// cycle->listening 数组中保存了监听的端口的信息
ls = ngx_array_push(&cf->cycle->listening);
ngx_memzero(ls, sizeof(ngx_listening_t));
// ngx_listening_t结构的sockaddr表示的是监听的端口的结构
sa = ngx_palloc(cf->pool, socklen);
ngx_memcpy(sa, sockaddr, socklen);
ls->sockaddr = sa;
ls->socklen = socklen;
//ngx_listening_t结构的addr_text就是监听的地址信息的字符串,包括端口
len = ngx_sock_ntop(sa, text, NGX_SOCKADDR_STRLEN, 1);
ls->addr_text.len = len;
switch (ls->sockaddr->sa_family) {
case AF_INET:
ls->addr_text_max_len = NGX_INET_ADDRSTRLEN;
break;
}
// ngx_listening_t结构的addr_text就是监听的地址信息的字符串,包括端口
ls->addr_text.data = ngx_pnalloc(cf->pool, len);
ngx_memcpy(ls->addr_text.data, text, len);
ls->fd = (ngx_socket_t) -1;
ls->type = SOCK_STREAM;// 表示监听的TCP
ls->backlog = NGX_LISTEN_BACKLOG;
ls->rcvbuf = -1;
ls->sndbuf = -1;
#if (NGX_HAVE_SETFIB)
ls->setfib = -1;
#endif
return ls;
}
上面还牵涉到一个比较简单的函数ngx_sock_ntop()
,这个函数的功能很简单,就是把sockaddr格式的ip地址转换为一个字符串
/*
*参数的含义:
* sa : 要转换为字符串的ip地址
* text: 保存转换之后的字符串
* len: sa参数的长度
* port: 0或者1,表示是否将端口也转换为字符串,如果为0则不转换,若为1则转换
* 返回值:转换的字符串的长度
*/
size_t
ngx_sock_ntop(struct sockaddr *sa, u_char *text, size_t len, ngx_uint_t port)
{
u_char *p;
struct sockaddr_in *sin;
switch (sa->sa_family) {
case AF_INET:
sin = (struct sockaddr_in *) sa;
p = (u_char *) &sin->sin_addr;
if (port) {
p = ngx_snprintf(text, len, "%ud.%ud.%ud.%ud:%d",
p[0], p[1], p[2], p[3], ntohs(sin->sin_port));
} else {
p = ngx_snprintf(text, len, "%ud.%ud.%ud.%ud",
p[0], p[1], p[2], p[3]);
}
return (p - text);
}
}
数据结构
本文牵涉到的数据结构比较多,整理如下:
typedef struct {
/* ngx_http_in_addr_t or ngx_http_in6_addr_t */
void *addrs; // ngx_http_in_addr_t 数组
ngx_uint_t naddrs; // 表示 addrs 数组元素的个数
} ngx_http_port_t;
typedef struct {
in_addr_t addr; // 当前监听的地址
ngx_http_addr_conf_t conf; //参考下面的数据结构
} ngx_http_in_addr_t;
typedef struct {
/* the default server configuration for this address:port */
ngx_http_core_srv_conf_t *default_server; // 当前address:port的default server
ngx_http_virtual_names_t *virtual_names; // 参考下面的数据结构
#if (NGX_HTTP_SSL)
ngx_uint_t ssl; /* unsigned ssl:1; */ // 表示listen指令是否使用了ssl
#endif
} ngx_http_addr_conf_t;
typedef struct {
// 参考下面的数据结构
// name的hash指向address:port的hash
// name的wc_head指向address:port的wc_head
// name的wc_tail指向address:port的wc_tail
// 也就是说把 address:port 的 hash,wc_head,wc_tail组合成一个ngx_hash_combined_t类型的数据结构,为后面做准备.
// 因为后面使用到的都是 ngx_http_port_t 结构体,不再使用address:port对应的ngx_http_conf_addr_t结构体
ngx_hash_combined_t names;
ngx_uint_t nregex;
ngx_http_server_name_t *regex;
} ngx_http_virtual_names_t;
typedef struct {
ngx_hash_t hash;
ngx_hash_wildcard_t *wc_head;
ngx_hash_wildcard_t *wc_tail;
} ngx_hash_combined_t;
喜欢本文的朋友们,欢迎长按下图关注订阅号郑尔多斯,更多精彩内容第一时间送达