您的位置:首页 > 其它

关于ngx中的socket初始化以及打开

2014-12-11 15:46 323 查看

http://www.cnblogs.com/fll369/archive/2012/11/29/2795128.html

1.  一些相关的数据结构:

//     监听端口配置信息,addrs是在该端口上所有监听地址的数组。
typedef struct {
ngx_int_t                  family;
in_port_t                  port;
ngx_array_t                addrs;     /* array of ngx_http_conf_addr_t */
} ngx_http_conf_port_t;


typedef struct {
ngx_http_listen_opt_t      opt;

ngx_hash_t                 hash;
ngx_hash_wildcard_t       *wc_head;
ngx_hash_wildcard_t       *wc_tail;

#if (NGX_PCRE)
ngx_uint_t                 nregex;
ngx_http_server_name_t    *regex;
#endif

/* the default server configuration for this address:port */
ngx_http_core_srv_conf_t  *default_server;
ngx_array_t                servers;  /* array of ngx_http_core_srv_conf_t */
} ngx_http_conf_addr_t;


监听地址配置信息,包含了所有在该addr:port监听的所有server块的ngx_http_core_srv_conf_t结构,以及hash、wc_head和wc_tail这些hash结构,保存了以server name为key,ngx_http_core_srv_conf_t为value的哈希表,用于快速查找对应虚拟主机的配置信息。

struct ngx_listening_s {
ngx_socket_t        fd;

struct sockaddr    *sockaddr;
socklen_t           socklen;    /* size of sockaddr */
size_t              addr_text_max_len;
ngx_str_t           addr_text;

int                 type;

void               *servers;  /* array of ngx_http_in_addr_t, for example */

ngx_log_t           log;
ngx_log_t          *logp;
}


2.监听socket的初始化

     nginx把需要监听的socket用ngx_listening_t表示,存放在ngx_cycle中的listening数组中.具体的socket初始化的可以分为3个步骤:1.解析配置文件,获取监听socket的相关信息. 2.初始化监听socket  3.打开并监听socket.

     1)解析配置文件获取socket相关信息

        用于设置监听socket的指令只要有2个:server_name,listen. server_name用于实现虚拟主机的功能,会为每个server块设置一个虚拟主机,在处理请求时会根据请求行中hosts进行转发请求.而listen就是监听socket的信息。这2个指令都在ngx_http_core_module中.首先看一下server_name指令的回调函数ngx_http_core_server_name,这个函数完成的功能很简单就是将server_name指令指定的虚拟主机名添加到ngx_http_core_srv_conf_t的server_names数组中,以便在后面对整个web
server支持的虚拟主机进行初始化

       a.  ngx_http_core_server_name:

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

u_char                   ch;
ngx_str_t               *value;
ngx_uint_t               i;
ngx_http_server_name_t  *sn;
.........
sn = ngx_array_push(&cscf->server_names);   //将指令sever_name的配置信息,添加到ngx_http_core_srv_conf_t中的sever_names数组中

.....
sn->server = cscf;

if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) {
sn->name = cf->cycle->hostname;

} else {
sn->name = value[i];
}

if (value[i].data[0] != '~') {
ngx_strlow(sn->name.data, sn->name.data, sn->name.len);
continue;
}
}


b.ngx_http_core_listen

      下面看一下listen指令的回调函数ngx_http_core_listen。这个函数主要还解析listen指令中的socket配置选项,并保存这些值,在函数的最后会调用ngx_http_add_listen函数添加监听socket的信息。

cscf->listen = 1;//core module的server config的listen置为1,表示该server块已经调用listen指令,设置了监听socket信息。如果listen等于0,即server块没有调用listen指令,后面会对监听信息进行默认初始化,比如监听的端口是80,地址是localhost等。

value = cf->args->elts;

ngx_memzero(&u, sizeof(ngx_url_t));

u.url = value[1];
u.listen = 1;
u.default_port = 80;
//解析url中的ip地址,端口号,
if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
if (u.err) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%s in \"%V\" of the \"listen\" directive",
u.err, &u.url);
}

return NGX_CONF_ERROR;
}


ngx_http_listen_opt_t   lsopt;
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));

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

lsopt.socklen = u.socklen;
lsopt.backlog = NGX_LISTEN_BACKLOG;
lsopt.rcvbuf = -1;
lsopt.sndbuf = -1;

      (void) ngx_sock_ntop(&lsopt.u.sockaddr, lsopt.addr,NGX_SOCKADDR_STRLEN, 1);//将2进至的地址信息,转换文件

ngx_http_listen_opt_t  用于存储listen socket配置信息.比如rcvbuf、sndbuf、backlog等,就是一些基本的socket选项.

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


在函数的最后部分调用了ngx_http_add_listen添加监听socket信息。在具体介绍这个函数实现之前,先来看一下nginx是如何存储监听socket的地址信息的。

 

在ngx_http_core_main_conf_t中有ports属性,保存nginx监听的所有端口的信息。ports是ngx_http_conf_port_t类型的数组,而每个ngx_http_conf_port_t结构又具有addrs属性,它存放了对应端口上要监听的地址。addrs是ngx_http_conf_addr_t类型的数组,ngx_http_conf_addr_t结构包含在addr:port上监听的虚拟主机名及对应的配置信息。

ngx_http_core_main_conf_t

    |---> prots: 监听的端口号的数组

                |---> ngx_http_conf_port_t:端口号的配置信息

                               |---> addrs:在该端口号上,监听的所有地址的数组

                                            |---> ngx_http_conf_addr_t:地址配置信息,包含在该addr:port上的多个虚拟主机

                                                           |---> servers:在addr:port上的说个server块的配置信息ngx_http_core_srv_conf_t

                                                           |            |---> ngx_http_core_srv_conf_t

                                                           |---> opt:ngx_http_listen_opt_t类型,监听socket的配置信息

                                                           |---> hash:以server_name为key,ngx_http_core_srv_conf_t为value的hash表,并且server_name不含通配符。

                                                           |---> wc_head:同hash,server_name含前缀通配符。

                                                           |---> wc_tail:同hash,server_name含后缀通配符。

 nginx就通过这种方式将监听的地址信息和端口信息组织起来,这些配置信息在后面回用于初始化ngx_cycle_t的listening属性等。下面看一下具体实现

 

c. ngx_http_add_listen

ngx_int_t
ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_listen_opt_t *lsopt)
{
port = ngx_array_push(cmcf->ports);  //将port放到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);
}


d .ngx_http_add_addresses

tatic ngx_int_t
ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
return ngx_http_add_address(cf, cscf, port, lsopt);
}


f ngx_http_add_address

static ngx_int_t
ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
ngx_http_conf_addr_t  *addr;

if (port->addrs.elts == NULL) {
if (ngx_array_init(&port->addrs, cf->temp_pool, 4,
sizeof(ngx_http_conf_addr_t))

addr = ngx_array_push(&port->addrs);

addr->opt = *lsopt;
addr->hash.buckets = NULL;
addr->hash.size = 0;
addr->wc_head = NULL;
addr->wc_tail = NULL;

addr->default_server = cscf;
addr->servers.elts = NULL;

return ngx_http_add_server(cf, cscf, addr);
}


static ngx_int_t
ngx_http_add_server(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_conf_addr_t *addr)
{
ngx_uint_t                  i;
ngx_http_core_srv_conf_t  **server;

if (addr->servers.elts == NULL) {
if (ngx_array_init(&addr->servers, cf->temp_pool, 4,
sizeof(ngx_http_core_srv_conf_t *))
!= NGX_OK)
{
return NGX_ERROR;
}

} else {
server = addr->servers.elts;
for (i = 0; i < addr->servers.nelts; i++) {
if (server[i] == cscf) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"a duplicate listen %s", addr->opt.addr);
return NGX_ERROR;
}
}
}

server = ngx_array_push(&addr->servers);
if (server == NULL) {
return NGX_ERROR;
}

*server = cscf;

return NGX_OK;
}


  最后,调用ngx_http_add_server将该server块的server config添加到addr的servers数组中,这个函数逻辑很简单,首先检查是否存在重复的server config,如果存在则报错,否则添加一个新的元素。

 2)初始化监听socket

我们介绍了在函数ngx_http_block函数中调用ngx_http_optimize_servers函数完成ngx_listening_t初始化。

    1. ngx_http_optimize_server 

static ngx_int_t
ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
ngx_array_t *ports)
{
ngx_uint_t             p, a;
ngx_http_conf_port_t  *port;
ngx_http_conf_addr_t  *addr;

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

ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);

addr = port[p].addrs.elts;
for (a = 0; a < port[p].addrs.nelts; a++) {

if (addr[a].servers.nelts > 1 )
{

初始addr(ngx_http_conf_addr_t)中的hash、wc_head和wc_tail哈希表。 
 这些哈希表以server name(虚拟主机名)为key,server块的ngx_http_core_srv_conf_t为 
 value,用于在处理请求时,根据请求的host请求行快速找到处理该请求的server配置结构。
if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
return NGX_ERROR;
}
}
}

if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
return NGX_ERROR;
}
}

return NGX_OK;
}


     这个函数就是遍历所有的端口号,将端口号对应的地址结构的hash、wc_head和wc_tail初始化,这个在初始化后面的ngx_listening_t的servers字段时会用到。然后调用ngx_http_init_listening函数完成ngx_listening_t初始化。

     2.ngx_http_init_listening

while (i < last) {
if (bind_wildcard && !addr[i].opt.bind) {
i++;
continue;
}
/**
* 添加ngx_listening_t
*/
ls = ngx_http_add_listening(cf, &addr[i]);
if (ls == NULL) {
return NGX_ERROR;
}

hport = ngx_pcalloc(cf->pool, sizeof(ngx_http_port_t));
if (hport == NULL) {
return NGX_ERROR;
}

/**
* servers会用来保存虚拟主机的信息,在处理请求时会赋值给request
* 用于进行虚拟主机的匹配
*/
ls->servers = hport;

if (i == last - 1) {
hport->naddrs = last;

} else {
hport->naddrs = 1;
i = 0;
}

/* 初始化ngx_http_virtual_names_t */
switch (ls->sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
if (ngx_http_add_addrs6(cf, hport, addr) != NGX_OK) {
return NGX_ERROR;
}
break;
#endif
default: /* AF_INET */
if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) {
return NGX_ERROR;
}
break;
}
addr++;
last--;
}


遍历port的addrs数组,因为每个ngx_http_conf_addr_t有opt属性,也就是ngx_listening_t的配置信息,这里会调用ngx_http_add_listening函数创建ngx_listening_t并添加到ngx_cycle_t中。ngx_http_add_addrs函数用于初始化ls->servers,这个属性主要是存放该监听socket对应的虚拟主机的信息,在处理请求时根据请求行的host匹配,选择对应的一个server块的ngx_http_core_srv_conf_t结构,这个结构里存放了刚请求处理的全局配置信息。下面看一下ngx_http_add_listening函数

   3. ngx_http_add_listening

ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);
if (ls == NULL) {
return NULL;
}


 调用ngx_create_listening函数创建ngx_listening_t,这个函数的内容在下面分析。

// 设置监听socket的handler,在监听到新的连接时调用,即ngx_event_accept中。
ls->handler = ngx_http_init_connection;


   设置ngx_listening_t的handler,这个handler会在监听到客户端连接时被调用,具体就是在ngx_event_accept函数中,ngx_http_init_connection函数顾名思义,就是初始化这个新建的连接。后面的代码就是根据addr的opt属性初始化创建的ngx_listening_t结构。下面看一下ngx_create_listengint函数。

     4. 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];

ls = ngx_array_push(&cf->cycle->listening);  //将ngx_listening_t中的放到数组cycle->listening中

ls->sockaddr = sa;

      ls->socklen = socklen;

      len = ngx_sock_ntop(sa, text, NGX_SOCKADDR_STRLEN, 1);

      ls->addr_text.len = len;

}


3.打开socket监听

   在ngx_init_cycle函数中,会调用ngx_open_listening_sockets和ngx_configure_listening_sockets函数完成监听socket的打开和配置

  

ngx_int_t
ngx_open_listening_sockets(ngx_cycle_t *cycle)
{
int               reuseaddr;
ngx_uint_t        i, tries, failed;
ngx_err_t         err;
ngx_log_t        *log;
ngx_socket_t      s;
ngx_listening_t  *ls;

reuseaddr = 1;
#if (NGX_SUPPRESS_WARN)
failed = 0;
#endif

log = cycle->log;

/* TODO: configurable try number */

for (tries = 5; tries; tries--) {
failed = 0;

/* for each listening socket */

ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {

if (ls[i].ignore) {
continue;
}

/**
* 已经从就cycle处继承该socket,不需重新打开
*/
if (ls[i].fd != -1) {
continue;
}

if (ls[i].inherited) {

/* TODO: close on exit */
/* TODO: nonblocking */
/* TODO: deferred accept */

continue;
}

/* 新建一个socket,socket(地址结构的协议族,socket类型tcp/udp,)*/
s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);

if (s == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_socket_n " %V failed", &ls[i].addr_text);
return NGX_ERROR;
}

if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
(const void *) &reuseaddr, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"setsockopt(SO_REUSEADDR) %V failed",
&ls[i].addr_text);

if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}

return NGX_ERROR;
}

#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)

if (ls[i].sockaddr->sa_family == AF_INET6 && ls[i].ipv6only) {
int  ipv6only;

ipv6only = (ls[i].ipv6only == 1);

if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
(const void *) &ipv6only, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"setsockopt(IPV6_V6ONLY) %V failed, ignored",
&ls[i].addr_text);
}
}
#endif
/* TODO: close on exit */

if (!(ngx_event_flags & NGX_USE_AIO_EVENT)) {
/* 将socket设置为非阻塞 */
if (ngx_nonblocking(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_nonblocking_n " %V failed",
&ls[i].addr_text);

if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}

return NGX_ERROR;
}
}

ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0,
"bind() %V #%d ", &ls[i].addr_text, s);

/* 将socket绑定到要监听的地址 */
if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
err = ngx_socket_errno;

if (err == NGX_EADDRINUSE && ngx_test_config) {
continue;
}

ngx_log_error(NGX_LOG_EMERG, log, err,
"bind() to %V failed", &ls[i].addr_text);

if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}

if (err != NGX_EADDRINUSE) {
return NGX_ERROR;
}

failed = 1;

continue;
}

#if (NGX_HAVE_UNIX_DOMAIN)

/* 处理unix domain socket */
if (ls[i].sockaddr->sa_family == AF_UNIX) {
mode_t   mode;
u_char  *name;

name = ls[i].addr_text.data + sizeof("unix:") - 1;
mode = (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);

if (chmod((char *) name, mode) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"chmod() \"%s\" failed", name);
}

if (ngx_test_config) {
if (ngx_delete_file(name) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
ngx_delete_file_n " %s failed", name);
}
}
}
#endif

/* 将socket转换为监听socket,backlog指定了内核为改监听socket排队的最大值 */
if (listen(s, ls[i].backlog) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"listen() to %V, backlog %d failed",
&ls[i].addr_text, ls[i].backlog);

if (ngx_close_socket(s) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[i].addr_text);
}

return NGX_ERROR;
}

/* 标识是监听socket */
ls[i].listen = 1;

/* 设置ngx_listening_t的描述符 */
ls[i].fd = s;
}

if (!failed) {
break;
}

/* TODO: delay configurable */

ngx_log_error(NGX_LOG_NOTICE, log, 0,
"try again to bind() after 500ms");

ngx_msleep(500);
}

if (failed) {
ngx_log_error(NGX_LOG_EMERG, log, 0, "still could not bind()");
return NGX_ERROR;
}

return NGX_OK;
}


   这个函数就是打开socket,和一般socket编程一样,就是调用socket、bind和listen.

    通过这3个步骤就完成了监听socket的初始化,接下来就会在ngx_event_process_init函数(ngx_event_core_moduel的process_init回调函数,在创建完worker进程后调用)中将这些监听socket添加到事件循环中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐