缘由
今天我又读了一遍 深入理解nginx的第八章。觉得又有了更深理解。不过由此带来了一个问题,书上既然说了ngx_listening_s的结构体是用于代表一个监听端口的。那么里面怎么没有一个int类型的成员代表端口呢?由这个问题,引来了我一系列探索和思考。基础知识
刚开始的时候还有点忘了,就是端口不是单独列出来的,是和结构体sockaddr和sockaddr_in有着密切的关系。请看下图:
其中参数sin_port就是代表端口。
那么服务器端的bind的函数是要用到这个sockaddr结构体的,bind之后就是可以listen了。
从配置文件中取值赋给ngx_cycle_t的成员
http指令
ngx_cycle_t这个结构体的位置非常特殊,每一个进程都有一个。经过的探索,发现是在ngx_create_listening()函数内,将端口等信息赋值了给了ngx_cycle_t结构体内的
- ngx_array_t listening;
listening这个动态数组,就是专门用来保存一系列监听端口的信息。
listening的结构体是ngx_listening_t,除了很多信息意外,与端口有关的就是:
struct ngx_listening_s {
ngx_socket_t fd;//文件描述符
struct sockaddr *sockaddr;
socklen_t socklen; /* size of sockaddr */
...
};
代码如下:
ngx_listening_t *
ngx_create_listening(ngx_conf_t *cf, void *sockaddr, socklen_t socklen)
{
...
ls = ngx_array_push(&cf->cycle->listening);
if (ls == NULL) {
return NULL;
}
ngx_memzero(ls, sizeof(ngx_listening_t));
sa = ngx_palloc(cf->pool, socklen);
if (sa == NULL) {
return NULL;
}
ngx_memcpy(sa, sockaddr, socklen);
ls->sockaddr = sa;
ls->socklen = socklen;
...
}
这段代码还有一个值得注意的就是我刚是还在想是怎么拿到ngx_cycle_t结构体的呢?原来是在每一个ngx_conf_t 对象里面都保留了一个ngx_cycle_t的引用,所以拿到了ngx_conf_t就可以拿到ngx_cycle_t。
这段代码是如何被调用的呢?如下图所示:
可以看到,调用到这个ngx_create_listening的原因是因为解析了配置文件。代码如下所示:
static ngx_command_t ngx_http_commands[] = {
{ ngx_string("http"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_http_block,
0,
0,
NULL },
ngx_null_command
};
就是因为解析到了http模块才会逐渐调用了ngx_create_listening这个函数。如果从运行的代码里来看的话。如下图所示:
所以,总结一下,正是因为在配置文件的http模块里写了linsten这个配置指令。那么在逐步解析的过程中,将listen里面值赋值给ngx_cycle_t的成员listening。这样该进程就可以使用在配置文件中写的值了。
lisnten指令
后来才想起来解析到listen的指令的时候也会单独调用一个方法:
{ ngx_string("listen"),
NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
ngx_http_core_listen,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL },
ngx_http_core_listen方法才真正去拿到配置项中
- listen 8080;
中的8080的函数。所以我推断,ngx_http_core_listen函数内确实是拿到8080这个值,如下图所示:
我的推断
我确实还没有完全搞清楚。但是我的推断是这样:
- 那就是ngx_cycle_t的listening动态数组,是一个存放了所有的sockaddr(里面有结构体)的地方。
- 但是单个读取还是要靠ngx_http_core_listen的函数。
- 那么ngx_http_core_listen先读取好了之后,就放在一个地方。
- 在合适的时候和地方把它给ngx_cycle_t的listening动态数组。
- 这样,才能方便之后的代码使用。
至于什么时候和什么地方。我还要再探索一下。
打开socket并监听
main函数会调用ngx_init_cycle,ngx_init_cycle非常复杂,我们先看看与监听窗口相关的函数,如下所示:ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
...
//先获得内存空间
cycle->listening.nelts = 0;
cycle->listening.size = sizeof(ngx_listening_t);
cycle->listening.nalloc = n;
cycle->listening.pool = pool;
...
//解析配置文件、赋值
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
...
//打开监听窗口
if (ngx_open_listening_sockets(cycle) != NGX_OK) {
goto failed;
}
...
}
接着看看ngx_open_listening_sockets函数,都一些关键的信息。除去一些不重要的内容,那么我们可以看到socket、bind、listen等服务器端的socket的函数的流程。
ngx_int_t
ngx_open_listening_sockets(ngx_cycle_t *cycle)
{
...
for (i = 0; i < cycle->listening.nelts; i++) {
s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);
if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
}
if (listen(s, ls[i].backlog) == -1) {
}
}
...
return NGX_OK;
}
额外问题
因为不涉及多进程,所以我把配置文件设置为master进程和worker进程是同一个进程的单进程模式。主要是
- master_process off;
- daemon off;