Nginx - 网络初始化

栏目: 服务器 · Nginx · 发布时间: 5年前

内容简介:Nginx在编译的时候会根据默认配置和我们指定的编译选项生成一个上一篇文章中提到work进程最终会进入首先来看EVENT核心模块对网络事件的初始化,EVENT模块的初始化主要是在

模块的初始化

Nginx在编译的时候会根据默认配置和我们指定的编译选项生成一个 ngx_modules.c 文件,这个文件包含了所有要编译进去的模块。 Nginx在启动时会调用 main -> ngx_init_cycle -> ngx_init_modules 函数来调用每个模块的 init_module 函数初始化所有模块, init_module 函数是用于模块级的初始化,实际上大多数模块都没有实现 init_module 函数:

ngx_int_t
ngx_init_modules(ngx_cycle_t *cycle)
{
    ngx_uint_t  i;
    /* 遍历所有模块,调用每个模块的init_module函数完成初始化工作 */
    for (i = 0; cycle->modules[i]; i++) {
        if (cycle->modules[i]->init_module) {
            if (cycle->modules[i]->init_module(cycle) != NGX_OK) {
                return NGX_ERROR;
            }
        }
    }

    return NGX_OK;
}

上一篇文章中提到work进程最终会进入 ngx_worker_process_cycle 函数,在这个函数里面首先会调用 ngx_worker_process_init 函数完成模块的进程级初始化,然后进入死循环等待事件处理。 在 ngx_worker_process_init 函数中会循环调用所有模块的 init_process 函数进行模块的进程级初始化:

for (i = 0; cycle->modules[i]; i++) {
        if (cycle->modules[i]->init_process) {
            if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
                /* fatal */
                exit(2);
            }
        }
    }

EVENT模块事件初始化

首先来看EVENT核心模块对网络事件的初始化,EVENT模块的初始化主要是在 init_process 函数中进行的,EVENT模块实现的 init_process 函数是 ngx_event_process_init 函数。 此函数的主要作用是初始化连接池,然后将监听套接字的事件处理函数挂载到事件循环中去,对于TCP来说当有请求到来时,调用 ngx_event_accept 函数来接收请求。

/* 设置监听套接字的事件处理函数为 ngx_event_accept */
        rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
                                                : ngx_event_recvmsg;

#if (NGX_HAVE_REUSEPORT)

        if (ls[i].reuseport) {
            /* 将监听套接字挂载到事件循环中去 */
            if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
                return NGX_ERROR;
            }

            continue;
        }

#endif

下面来看epoll的事件处理函数 ngx_epoll_process_events ,epoll_wait等待内核唤醒处理事件,如果有可读事件则调用rev->handler函数来处理, 如果有可写事件则调用wev->handler来处理:

static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
    ···
    /* 首先epoll_wait进入内核等待事件唤醒 */
    events = epoll_wait(ep, event_list, (int) nevents, timer);

    ···
    /* 开始循环处理事件 */
    for (i = 0; i < events; i++) {
        c = event_list[i].data.ptr;

        instance = (uintptr_t) c & 1;
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);

        rev = c->read;
        ···

        /* 处理可读事件 */
        if ((revents & EPOLLIN) && rev->active) {

            rev->ready = 1;

            if (flags & NGX_POST_EVENTS) {
                queue = rev->accept ? &ngx_posted_accept_events
                                    : &ngx_posted_events;

                ngx_post_event(rev, queue);

            } else {
                /* 调用对应套接字的可读事件处理函数 */
                rev->handler(rev);
            }
        }

        wev = c->write;

        /* 处理可写事件 */
        if ((revents & EPOLLOUT) && wev->active) {

            if (c->fd == -1 || wev->instance != instance) {

                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                               "epoll: stale event %p", c);
                continue;
            }

            wev->ready = 1;

            if (flags & NGX_POST_EVENTS) {
                ngx_post_event(wev, &ngx_posted_events);

            } else {
                /* 调用对应套接字的可写事件处理函数 */
                wev->handler(wev);
            }
        }
    }

    return NGX_OK;
}

对于监听套接字来说,有可读时间就会进入 ngx_event_accept 函数来接收请求。 ngx_event_accept 函数首先调用 accept 函数接收TCP套接字请求, 对接收到的新套接字创建一个 ngx_connection_t 连接并初始化,然后调用注册进监听套接字 ngx_listening_t 结构体中的 handler 函数进行下一步处理。 对于HTTP请求来说这个 handler 函数由HTTP模块注册,下面会讲到。

HTTP模块请求初始化

再来看HTTP核心模块对HTTP请求的初始化, HTTP模块是在nginx启动阶段读取配置时调用HTTP模块的配置解析函数 ngx_http_block 进行初始化的,调用链如下: main -> ngx_init_cycle -> ngx_conf_parse -> ngx_conf_handler , 在 ngx_conf_parse 函数中读取并解析配置,然后调用 ngx_conf_handler 函数来查找此配置是属于哪个模块的,找到后调用对应模块注册时各自实现的 set 函数来处理配置。

对于HTTP模块来说它注册的 set 函数是 ngx_http_block ,所以当遇到 http 这个配置项时就会调用 ngx_http_block 来解析配置并初始化。

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
};

ngx_http_block 函数的作用主要是解析 http 块里面的配置项,然后注册HTTP请求的处理函数,注册调用链如下: ngx_http_block -> ngx_http_optimize_servers -> ngx_http_init_listening -> ngx_http_add_listeningngx_http_add_listening 函数中会新建一个 ngx_listening_t 结构体并初始化,其中的 handler 函数设置为 ngx_http_init_connection 。 所以上一节提到接收TCP请求之后调用 handler 函数,就会进入 ngx_http_init_connection 函数中去。

ngx_http_init_connection 函数的作用主要是将新接收的套接字读事件处理函数设置为 ngx_http_wait_request_handler ,然后设置超时时间,最后将套接字加入到event事件循环中。 当发送HTTP请求时就会直接调用 ngx_http_wait_request_handler 函数来处理HTTP请求头。

void
ngx_http_init_connection(ngx_connection_t *c)
{
    ···
    /* the default server configuration for the address:port */
    hc->conf_ctx = hc->addr_conf->default_server->ctx;

    ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
    if (ctx == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    ctx->connection = c;
    ctx->request = NULL;
    ctx->current_request = NULL;

    c->log->connection = c->number;
    c->log->handler = ngx_http_log_error;
    c->log->data = ctx;
    c->log->action = "waiting for request";

    c->log_error = NGX_ERROR_INFO;

    rev = c->read;
    /* 设置读事件处理函数 */
    rev->handler = ngx_http_wait_request_handler;
    c->write->handler = ngx_http_empty_handler;

    if (hc->addr_conf->proxy_protocol) {
        hc->proxy_protocol = 1;
        c->log->action = "reading PROXY protocol";
    }

    if (rev->ready) {
        /* the deferred accept(), iocp */

        if (ngx_use_accept_mutex) {
            ngx_post_event(rev, &ngx_posted_events);
            return;
        }

        rev->handler(rev);
        return;
    }

    /* 设置超时时间 */
    ngx_add_timer(rev, c->listening->post_accept_timeout);
    ngx_reusable_connection(c, 1);

    /* 将套接字添加到event事件循环中 */
    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        ngx_http_close_connection(c);
        return;
    }
}

总结

至此Nginx的网络请求处理流程就基本梳理清楚了,接下来分析HTTP请求的具体处理过程。

上面的分析使用的是epoll模型,并且开启了port_reuse选项。

Nginx网络请求的处理流程大概是:

  1. Nginx的master进程根据work进程的数量N,创建N个监听套接字。
  2. event事件模块将监听套接字加入到epoll事件中,并设置事件处理函数为 ngx_event_accept 函数。
  3. 当有请求到来时,epoll事件处理函数直接调用 ngx_event_accept 函数来接收请求并调用HTTP模块注册的处理函数 ngx_http_init_connection
  4. http模块将接收到的新套接字加入到epoll事件中,并设置事件处理函数为 ngx_http_wait_request_handler 函数。
  5. 此时TCP连接建立完成,当发送HTTP请求时,epoll事件处理函数直接调用 ngx_http_wait_request_handler 函数来处理请求。

这里还有一个疑问,work进程是怎么进入epoll事件处理函数的?

work进程在 ngx_worker_process_cycle 函数中无限循环调用 ngx_process_events_and_timers 函数,此函数会调用 ngx_process_events 函数,此函数就是epoll注册的事件处理函数 ngx_epoll_process_events

基于nginx-1.14.0源码分析


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

测试驱动开发

测试驱动开发

Kent Beck / 孙平平、张小龙 / 中国电力出版社 / 2004-4-1 / 28.00元

《测试驱动开发》(中文版)设想把编程看成是转动曲柄从井里提一桶水上来的过程。如果水桶比较小,那么仅需一个能自由转动的曲柄就可以了。如果水桶比较大而且装满水,那么还没等水桶全部被提上来你就会很累了。你需要一个防倒转的装置,以保证每转一次可以休息一会儿。水桶越重,防倒转的棘齿相距越近。测试驱动开发中的测试程序就是防倒转装置上的棘齿。一旦我们的某个测试程序能工作了,你就知道,它从现在开始并且以后永远都可......一起来看看 《测试驱动开发》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器