nginx监听事件流程
在前面的几篇文章中已经分析了master进程、work进程的初始化流程。但一直没有分析监听socket的创建流程,nginx服务器只有在创建socket, 绑定socet,监听socket执行完成后,才能处理来自客户端的连接。ngx_cycle_t结构中有一个listening成员,存放的就是所有监听socket。接下来首先分析socket内部结构的维护,不同域名的管理,然后分析什么时候把监听socket添加到listening数组中,最后分析何时创建监听socket。
一、socket内部结构管理
static ngx_command_t ngx_http_core_commands[] = { { ngx_string("listen"), NGX_HTTP_SRV_CONF|NGX_CONF_1MORE, ngx_http_core_listen, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL }, }在ngx_http_core_module模块的命令列表中,listen命令的实现函数为ngx_http_core_listen。nginx.conf配置文件中,每一个server块都可以监听不同的端口,listen命令函数的作用就一个,就是为了维护socket的内部结构。如果直接分析源代码,则不好表达,也不太好理解。还是以一个例子来说明socket内部结构的维护吧!
http { #server1,监听80端口,ip为192.168.100.1 server { listen 192.168.100.1:80; server_name www.server1.com; } #server2,监听80端口,ip位192.168.100.2 server { listen 192.168.100.2:80; server_name www.server2.com; } #server3,监听80端口,ip位192.168.100.1 server { listen 192.168.100.1:80; server_name www.server3.com; } #server4,监听90端口,ip位192.168.100.4 server { listen 192.168.100.4:90; server_name www.server4.com; } }假设有这样一个nginx.conf配置文件。192.168.100.1 ---192.168.100.2这两个ip监听同一个端口80, 而 192.168.100.4监听端口90。nginx一共监听了2个端口,则nginx维护的内部结构如下图:
在这张图中,192.168.100.1 ---192.168.100.2这两个ip监听端口80。192.168.100.1:80这个socket对应有www.server1.com; www.server3.com共两个域名。 192.168.100.2:80这个socket对应有www.server2.com共一个域名。
192.168.100.4这个ip监听90端口,对应的域名为www.server4.com
nginx维护这样的结构是为了做什么? 假设nginx在192.168.100.1 :80这个socket收到来自客户端的连接,http请求头部的host=www.server3.com。则查找过程如下:
(1)先在ports数组中查找80端口对应的结构ngx_http_conf_port_t;
(2)接着查找ngx_http_conf_port_t中的addrs数组,从而获取到192.168.100.1所在的ngx_http_conf_addr_t;
(3)然后查找ngx_http_conf_addr_t结构中的servers数组,从而获取得到www.server3.com域名所在的server块。
从这个例子可以看出,即便来自同一个ip:port的客户端连接,由于请求域名的不同,从而会获取到不同的server块进行处理。而listen命令的函数ngx_http_core_listen就是用来维护这样的一种socket结构
//解析listen命令 static char * ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { cscf->listen = 1; //表示server块配置了listen配置项 value = cf->args->elts; u.url = value[1]; u.listen = 1; u.default_port = 80; //解析listen的参数1,获取监听的ip地址以及端口信息 ngx_parse_url(cf->pool, &u); ngx_memcpy(&lsopt.u.sockaddr, u.sockaddr, u.socklen); //给监听选项结构赋值默认值 lsopt.socklen = u.socklen; lsopt.backlog = NGX_LISTEN_BACKLOG; lsopt.rcvbuf = -1; lsopt.sndbuf = -1; //将struct sockaddr结构转为点分十进制的ip地址格式,例如172.16.3.180 (void) ngx_sock_ntop(&lsopt.u.sockaddr, lsopt.addr, NGX_SOCKADDR_STRLEN, 1); //listen配置的参数保存到监听选项结构 for (n = 2; n < cf->args->nelts; n++) { //设置默认的server块 if (ngx_strcmp(value[n].data, "default_server") == 0 || ngx_strcmp(value[n].data, "default") == 0) { lsopt.default_server = 1; continue; } //设置监听队列大小 if (ngx_strncmp(value[n].data, "backlog=", 8) == 0) { lsopt.backlog = ngx_atoi(value[n].data + 8, value[n].len - 8); lsopt.set = 1; lsopt.bind = 1; if (lsopt.backlog == NGX_ERROR || lsopt.backlog == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid backlog "%V"", &value[n]); return NGX_CONF_ERROR; } continue; } } //将server块添加到监听端口中,表示有多少个server块在监听同一个端口 if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) { return NGX_CONF_OK; } return NGX_CONF_ERROR; }而ngx_parse_url函数是从listen配置项格式中提取到需要监听的ip地址,端口号等信息。
//根据listen命令格式"listen ip:port"获取ip, port,保存到u对应的成员中 ngx_int_t ngx_parse_url(ngx_pool_t *pool, ngx_url_t *u) { u_char *p; p = u->url.data; //格式: listen unix:/var/run/nginx.sock if (ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0) { return ngx_parse_unix_domain_url(pool, u); } if ((p[0] == ":" || p[0] == "/") && !u->listen) { u->err = "invalid host"; return NGX_ERROR; } //格式: listen [fe80::1]; if (p[0] == "[") { return ngx_parse_inet6_url(pool, u); } //根据url获取ipv4格式的地址信息() return ngx_parse_inet_url(pool, u); }ngx_http_add_listen这个函数开始就是要创建图中的ports数组内容了,使得nginx能监听不同的端口。
//将server块添加到监听端口中,表示有多少个server块在监听同一个端口 ngx_int_t ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_http_listen_opt_t *lsopt) { cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); //创建监听端口数组 if (cmcf->ports == NULL) { cmcf->ports = ngx_array_create(cf->temp_pool, 2, sizeof(ngx_http_conf_port_t)); } sa = &lsopt->u.sockaddr; //获取监听端口 switch (sa->sa_family) { default: /* AF_INET */ sin = &lsopt->u.sockaddr_in; p = sin->sin_port; break; } //查找是否存在相同的监听端口,相同则只添加server块 port = cmcf->ports->elts; for (i = 0; i < cmcf->ports->nelts; i++) { if (p != port[i].port || sa->sa_family != port[i].family) { continue; } //查找到有多个ip监听同一个端口时的处理逻辑 return ngx_http_add_addresses(cf, cscf, &port[i], lsopt); } //指向到此,说明没有查找到相同的监听端口,则需要创建一个端口 port = ngx_array_push(cmcf->ports); port->family = sa->sa_family; port->port = p; port->addrs.elts = NULL; //如果有多个不同的ip监听同一个端口,则需要把这些ip信息保持到ngx_http_conf_port_t结构中的addrs数组中 return ngx_http_add_address(cf, cscf, port, lsopt); }如果有多个不同的ip监听同一个端口,则需要把这些ip信息保持到ngx_http_conf_port_t结构中的addrs数组中。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) { if (port->addrs.elts == NULL) { ngx_array_init(&port->addrs, cf->temp_pool, 4, sizeof(ngx_http_conf_addr_t) } //同一个监听端口,但对于不同的ip逻辑,则需要创建一个ngx_http_conf_addr_t结构。 //表示有多个ip地址监听同一个端口 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; //多个server块监听同一个端口时,有一个默认的server块 addr->servers.elts = NULL; //将监听同一个端口的server块加入到server数组中 return ngx_http_add_server(cf, cscf, addr); }最终将将监听ip:port的server保存到数组中,则是由ngx_http_add_server这个函数完成。
//将监听同一个端口的server块加入到server数组中 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) { ngx_array_init(&addr->servers, cf->temp_pool, 4, sizeof(ngx_http_core_srv_conf_t *) } else { //查找是否同一个server块两次调用listen监听同一个端口 //在同一个server块中调用两次listen监听同一个端口是不允许的;例如: //listen 80; listen 80;是非法的 server = addr->servers.elts; for (i = 0; i < addr->servers.nelts; i++) { if (server[i] == cscf) { return NGX_ERROR; } } } //获取一个server块 server = ngx_array_push(&addr->servers); if (server == NULL) { return NGX_ERROR; } //保存server块,表示这个server块监听ip:portsocket *server = cscf; }到此为止,nginx对于监听socket维护的内部结构已经分析完成了。接下里分析下nginx服务器对不同域名的管理
二、域名管理
在解析http配置块中,调用了ngx_http_optimize_servers函数,将把各个监听端口下的所有域名加入到相应哈希表中(例如:普通哈希表,前置通配符哈希表,后置通配符哈希表)。这样nginx在收到客户端连接请求时,就可以直接根据http请求头部host字段查找这些哈希表,从而获取到server块,由这个server块处理这个http请求。
//开始解析http块 static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { //功能: 将所有监听socket对应的服务器名加入到哈希表中。例如:有80?90两个端口。则将80端口所有server块下的 //所有服务器名加入到哈希表;将90端口所有server块下的所有服务器名加入到哈希表 if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) { return NGX_CONF_ERROR; } }ngx_http_optimize_servers这个函数负责将各个监听端口下的所有域名加入到相应哈希表中;该函数同时也会创建一个监听对象
//功能: 将所有监听socket对应的服务器名加入到哈希表中。例如:有80?90两个端口。则将80端口所有server块下的 //所有服务器名加入到哈希表;将90端口所有server块下的所有服务器名加入到哈希表 static ngx_int_t ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf, ngx_array_t *ports) { //对于每一个监听端口,都会有对应多个server块监听这个端口。这每一个server块又可能有多少server名称。 //下面这个循环处理每一个端口,构成一个哈希表 port = ports->elts; for (p = 0; p < ports->nelts; p++) { //将把监听同一个端口的所有ip信息排序 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++) { //处理监听同一个端口的所有server块,构成一个哈希表 if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) { return NGX_ERROR; } } //创建ngx_listening_s对象,并用port数组给这个对象赋值 if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) { return NGX_ERROR; } } }函数ngx_http_server_names将创建由所有域名构成的普通哈希表,前置通配符哈希表、后置通配符哈希表。这样在收到来自客户端的请求时,可以根据http头部的host字段查找这些哈希表,进而获取到server块。
//创建服务名哈希表 static ngx_int_t ngx_http_server_names(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf, ngx_http_conf_addr_t *addr) { //创建一个哈希数组 if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) { goto failed; } cscfp = addr->servers.elts; //多个server块监听同一个端口 for (s = 0; s < addr->servers.nelts; s++) { name = cscfp[s]->server_names.elts; //每一个server块又可能有多个服务器名 for (n = 0; n < cscfp[s]->server_names.nelts; n++) { //将服务器名加入到哈希数组中 rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server,NGX_HASH_WILDCARD_KEY); } } //创建普通哈希表 if (ha.keys.nelts) { hash.hash = &addr->hash; hash.temp_pool = NULL; if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) { goto failed; } } //创建前置哈希表 if (ha.dns_wc_head.nelts) { ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts, sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards); ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts, ha.dns_wc_head.nelts); addr->wc_head = (ngx_hash_wildcard_t *) hash.hash; } //创建后置通配符哈希表 if (ha.dns_wc_tail.nelts) { ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts,ha.dns_wc_tail.nelts); addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash; } }
三、监听对象的创建
而ngx_http_init_listening函数将创建一个创建一个ngx_listening_t对象,并个这个对象的成员赋值。同时将创建后的这个对象存放到cf->cycle->listening这个监听数组中保存。static ngx_int_t ngx_http_init_listening(ngx_conf_t *cf, ngx_http_conf_port_t *port) { while (i < last) { //创建一个ngx_listening_t对象,并个这个对象的成员赋值。例如设置监听回调 ls = ngx_http_add_listening(cf, &addr[i]); if (ls == NULL) { return NGX_ERROR; } } }
ngx_http_add_listening函数创建监听对象,并设置监听的回调为ngx_http_init_connection。在监听到客户端的连接时,这个回调将被调用。
//创建一个ngx_listening_t对象,并个这个对象的成员赋值。例如设置监听回调 static ngx_listening_t * ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) { //创建一个ngx_listening_t对象 ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen); ls->addr_ntop = 1; //监听回调 ls->handler = ngx_http_init_connection; //在有多少server块监听同一个端口时,使用默认块的配置 cscf = addr->default_server; ls->pool_size = cscf->connection_pool_size; ls->post_accept_timeout = cscf->client_header_timeout; return ls; }四、监听socket
在函数ngx_init_cycle有这样的代码段,用来创建socket,绑定socekt,监听socket
//初始化ngx_cycle_t结构 ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle) { //创建监听数组中的所有socket if (ngx_open_listening_sockets(cycle) != NGX_OK) { goto failed; } //获取发送缓冲区,接收缓冲区大小,以及监听socket ngx_configure_listening_sockets(cycle); }而函数ngx_open_listening_sockets将对监听数组中存放的所有ip,port , 执行socket, 绑定socket, 监听socket操作。如果失败,则最多重复执行5次。
//创建监听数组中的所有socket ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle) { //因此创建soceket,绑定socket, 监听socket等操作有可能调用失败。 //失败后最多尝试5次 for (tries = 5; tries; tries--) { //将对所有监听数组中的ip,port,开始创建socket ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { //创建套接字 s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0); //绑定套接字 bind(s, ls[i].sockaddr, ls[i].socklen); //监听套接字 listen(s, ls[i].backlog); ls[i].listen = 1; ls[i].fd = s; } ngx_msleep(500); } }而ngx_configure_listening_sockets(ngx_cycle_t *cycle)函数只是简单设置socket的一些选项,例如设置发送缓冲区,接收缓冲区大小,以及监听队列大小等。
到此监听事件已经分析完了,至于把监听socket加入到epoll中,则在ngx_trylock_accept_mutex函数中完成。将监听socket加入到epoll在前面的文章中已经分析过了,在这里就不在分析了,可以参考master进程、work进程初始化这两篇文章。
- 上一篇:没有了
- 下一篇:没有了