• Nginx事件管理之ngx_event_core_module模块


    1. 概述

    ngx_event_core_module 模块是一个事件类型的模块,它在所有事件模块中的顺序是第一位。它主要完成以下两点任务:

    • 创建连接池(包括读/写事件);
    • 决定究竟使用哪些事件驱动机制,并初始化将要使用的事件模块

    2. 配置项

    static ngx_command_t  ngx_event_core_commands[] = {
    
        /* 连接池的大小,也就是每个 worker 进程中支持的 TCP 最大连接数 */
        { ngx_string("worker_connections"),
          NGX_EVENT_CONF|NGX_CONF_TAKE1,
          ngx_event_connections,
          0,
          0,
          NULL },
    
        /* 确定选择哪一个事件模块作为事件驱动机制 */
        { ngx_string("use"),
          NGX_EVENT_CONF|NGX_CONF_TAKE1,
          ngx_event_use,
          0,
          0,
          NULL },
    
        /* 对应事件定义 ngx_event_s 结构体的成员 available 字段。对于 epoll 事件驱动模式来说,
         * 意味着在接收到一个新连接事件时,调用 accept 以尽可能多地接收连接 */
        { ngx_string("multi_accept"),
          NGX_EVENT_CONF|NGX_CONF_FLAG,
          ngx_conf_set_flag_slot,
          0,
          offsetof(ngx_event_conf_t, multi_accept),
          NULL },
    
        /* 确定是否使用 accept_mutex 负载均衡锁,默认为开启 */
        { ngx_string("accept_mutex"),
          NGX_EVENT_CONF|NGX_CONF_FLAG,
          ngx_conf_set_flag_slot,
          0,
          offsetof(ngx_event_conf_t, accept_mutex),
          NULL },
    
        /* 启用 accept_mutex 负载均衡锁后,延迟 accept_mutex_delay 毫秒后再试图处理新连接事件 */
        { ngx_string("accept_mutex_delay"),
          NGX_EVENT_CONF|NGX_CONF_TAKE1,
          ngx_conf_set_msec_slot,
          0,
          offsetof(ngx_event_conf_t, accept_mutex_delay),
          NULL },
    
        /* 需要对来自指定 IP 的 TCP 连接打印 debug 级别的调试日志 */
        { ngx_string("debug_connection"),
          NGX_EVENT_CONF|NGX_CONF_TAKE1,
          ngx_event_debug_connection,
          0,
          0,
          NULL },
    
          ngx_null_command
    };
    

    2.1 ngx_event_conf_t 结构体

    typedef struct {
        /* 连接池的大小 */
        ngx_uint_t    connections;
        /* 选用的事件模块在所有事件模块中的序号,即 ctx_index 成员 */
        ngx_uint_t    use;
    
        /* 标志位,为 1 时表示在接收到一个新连接事件时,一次性建立尽可能多的连接 */
        ngx_flag_t    multi_accept;
        /* 标志位,为 1 时表示启用负载均衡锁 */
        ngx_flag_t    accept_mutex;
    
        /* 负载均衡锁会使有些 worker 进程在拿不到锁时延迟建立新连接,accept_mutex_delay 
         * 就是这段延迟时间的长度 */
        ngx_msec_t    accept_mutex_delay;
    
        /* 所选用事件模块的名字,它与 use 成员是匹配的 */
        u_char       *name;
    
    #if (NGX_DEBUG)
        /* 在 --with-debug 编译模式下,可以仅针对某些客户端建立的连接输出调试级别的日志,
         * 而 debug-connection 数组用于保存这些客户端的地址信息 */
        ngx_array_t   debug_connection;
    #endif
    } ngx_event_conf_t;
    

    3. 事件模块公共接口:ngx_event_module_t

    static ngx_event_module_t  ngx_event_core_module_ctx = {
        &event_core_name,
        ngx_event_core_create_conf,            /* create configuration */
        ngx_event_core_init_conf,              /* init configuration */
    
        { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
    };
    

    从这可以看出,ngx_event_core_module 模块仅实现了 create_conf 方法和 init_conf 方法,这是因为它并不真正负责
    TCP 网络事件的驱动,所以不会实现 ngx_event_actions_t 中的方法。

    ngx_event_core_create_conf 方法仅是为 ngx_event_conf_t 结构体分配内存空间,并初始化其成员值。

    ngx_event_core_init_conf 方法则会根据系统平台选择一个合适的事件处理模块,此外,还对一些重要的但是没有在
    nginx.conf 中配置的值 进行初始化。

    static char *ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf)
    {
        TRACE_DEBUG(NGX_LOG_INFO, "ngx_event_core_module: init conf");
    
        ngx_event_conf_t  *ecf = conf;
    
    #if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
        int                  fd;
    #endif
        ngx_int_t            i;
        ngx_module_t        *module;
        ngx_event_module_t  *event_module;
    
        module = NULL;
    
        /* Nginx根据当前系统平台选择一个合适的事件处理模块 */
    
    #if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
    
        fd = epoll_create(100);
    
        if (fd != -1)
        {
            (void)close(fd);
            module = &ngx_epoll_module;
        }
        else if (ngx_errno != NGX_ENOSYS)
        {
            module = &ngx_epoll_module;
        }
        
    #endif
    
    #if (NGX_HAVE_DEVPOLL) && !(NGX_TEST_BUILD_DEVPOLL)
    
        module = &ngx_devpoll_module;
        
    #endif
    
    #if (NGX_HAVE_KQUEUE)
    
        module = &ngx_kqueue_module;
    
    #endif
    
    #if (NGX_HAVE_SELECT)
    
        if (module == NULL)
        {
            module = &ngx_select_module;
        }
    
    #endif
    
        if (module == NULL)
        {
            for (i = 0; cycle->modules[i]; i++)
            {
                if (cycle->modules[i]->type != NGX_EVENT_MODULE)
                {
                    continue;
                }
    
                event_module = cycle->modules[i]->ctx;
    
                if (ngx_strcmp(event_module->name->data, event_core_name.data) == 0)
                {
                    continue;
                }
    
                module = cycle->modules[i];
                break;
            }
        }
    
        if (module == NULL)
        {
            ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no events module found");
            return NGX_CONF_ERROR;
        }
    
        ngx_conf_init_uint_value(ecf->connections, DEFAULT_CONNEXTIONS);
        cycle->connection_n = ecf->connections;
    
        /* 把该事件处理模块序号记录在配置变量ecf->use中 */
        ngx_conf_init_uint_value(ecf->use, module->ctx_index);
    
        event_module = module->ctx;
        ngx_conf_init_ptr_value(ecf->name, event_module->name->data);
    
        ngx_conf_init_value(ecf->multi_accept, 0);
        ngx_conf_init_value(ecf->accept_mutex, 0);
        ngx_conf_init_msec_value(ecf->accept_mutex_delay, 500);
    
        return NGX_CONF_OK;
    }
    

    4. ngx_event_core_module 模块的定义

    /* 该模块会决定使用哪种事件驱动机制,以及如何管理事件 */
    ngx_module_t  ngx_event_core_module = {
        NGX_MODULE_V1,
        &ngx_event_core_module_ctx,            /* module context */
        ngx_event_core_commands,               /* module directives */
        NGX_EVENT_MODULE,                      /* module type */
        NULL,                                  /* init master */
        /* 没有 fork 出 worker 子进程时,会调用该函数 */
        ngx_event_module_init,                 /* init module */
        /* fork出子进程后,每一个worker进程会在用 ngx_event_core_module 模块的
         * ngx_event_process_init 方法后才会进入正式的工作循环 */
        ngx_event_process_init,                /* init process */
        NULL,                                  /* init thread */
        NULL,                                  /* exit thread */
        NULL,                                  /* exit process */
        NULL,                                  /* exit master */
        NGX_MODULE_V1_PADDING
    };
    

    4.1 ngx_event_module_init()

    static ngx_int_t ngx_event_module_init(ngx_cycle_t *cycle)
    {
        void              ***cf;
        u_char              *shared;
        size_t               size, cl;
        ngx_shm_t            shm;
        ngx_time_t          *tp;
        ngx_core_conf_t     *ccf;
        ngx_event_conf_t    *ecf;
    
        /* 获取 ngx_events_module 模块持有的关于事件模块的总配置项结构体指针数组 */
        cf = ngx_get_conf(cycle->conf_ctx, ngx_events_module);
        /* 在总配置项结构体指针数组中获取 ngx_event_core_module 模块的配置项结构体 */
        ecf = (*cf)[ngx_event_core_module.ctx_index];
    
        if (!ngx_test_config && ngx_process <= NGX_PROCESS_MASTER) {
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                          "using the "%s" event method", ecf->name);
        }
    
        ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
    
        ngx_timer_resolution = ccf->timer_resolution;
    
    #if !(NGX_WIN32)
        {
        ngx_int_t      limit;
        struct rlimit  rlmt;
    
        if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "getrlimit(RLIMIT_NOFILE) failed, ignored");
    
        } else {
            if (ecf->connections > (ngx_uint_t) rlmt.rlim_cur
                && (ccf->rlimit_nofile == NGX_CONF_UNSET
                    || ecf->connections > (ngx_uint_t) ccf->rlimit_nofile))
            {
                limit = (ccf->rlimit_nofile == NGX_CONF_UNSET) ?
                             (ngx_int_t) rlmt.rlim_cur : ccf->rlimit_nofile;
    
                ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
                              "%ui worker_connections exceed "
                              "open file resource limit: %i",
                              ecf->connections, limit);
            }
        }
        }
    #endif /* !(NGX_WIN32) */
    
    
        if (ccf->master == 0) {
            return NGX_OK;
        }
    
        if (ngx_accept_mutex_ptr) {
            return NGX_OK;
        }
    
    
        /* cl should be equal to or greater than cache line size */
    
        cl = 128;
    
        size = cl            /* ngx_accept_mutex */
               + cl          /* ngx_connection_counter */
               + cl;         /* ngx_temp_number */
    
    #if (NGX_STAT_STUB)
    
        size += cl           /* ngx_stat_accepted */
               + cl          /* ngx_stat_handled */
               + cl          /* ngx_stat_requests */
               + cl          /* ngx_stat_active */
               + cl          /* ngx_stat_reading */
               + cl          /* ngx_stat_writing */
               + cl;         /* ngx_stat_waiting */
    
    #endif
    
        shm.size = size;
        ngx_str_set(&shm.name, "nginx_shared_zone");
        shm.log = cycle->log;
    
        if (ngx_shm_alloc(&shm) != NGX_OK) {
            return NGX_ERROR;
        }
    
        shared = shm.addr;
    
        ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
        ngx_accept_mutex.spin = (ngx_uint_t) -1;
    
        if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared,
                             cycle->lock_file.data)
            != NGX_OK)
        {
            return NGX_ERROR;
        }
    
        ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);
    
        (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);
    
        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "counter: %p, %uA",
                       ngx_connection_counter, *ngx_connection_counter);
    
        ngx_temp_number = (ngx_atomic_t *) (shared + 2 * cl);
    
        tp = ngx_timeofday();
    
        ngx_random_number = (tp->msec << 16) + ngx_pid;
    
    #if (NGX_STAT_STUB)
    
        ngx_stat_accepted = (ngx_atomic_t *) (shared + 3 * cl);
        ngx_stat_handled = (ngx_atomic_t *) (shared + 4 * cl);
        ngx_stat_requests = (ngx_atomic_t *) (shared + 5 * cl);
        ngx_stat_active = (ngx_atomic_t *) (shared + 6 * cl);
        ngx_stat_reading = (ngx_atomic_t *) (shared + 7 * cl);
        ngx_stat_writing = (ngx_atomic_t *) (shared + 8 * cl);
        ngx_stat_waiting = (ngx_atomic_t *) (shared + 9 * cl);
    
    #endif
    
        return NGX_OK;
    }
    

    该方法主要初始化了一些变量,尤其是 ngx_http_stub_status_module 统计模块使用的一些原子性的统计变量。

    4.2 ngx_event_process_init()

    static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
    {
        ngx_uint_t           m, i;
        ngx_event_t         *rev, *wev;
        ngx_listening_t     *ls;
        ngx_connection_t    *c, *next, *old;
        ngx_core_conf_t     *ccf;
        ngx_event_conf_t    *ecf;
        ngx_event_module_t  *module;
    
        ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
        ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
    
        /* 当打开 accept_mutex 负载均衡锁,同时使用了 master 模式且 worker 进行数量大于 1 时,
         * 才正式确定了进程将使用 accept_mutex 负载均衡锁。因此,即使我们在配置文件中指定打开
         * accept_mutex 锁,如果没有使用 master 模式或者 worker 进程数量等于 1,进程在运行时
         * 还是不会使用负载均衡锁(既然不存在多个进程去抢一个监听端口上的连接的情况,自然就不
         * 需要均衡多个 worker 进程的负载)*/
        if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
            /* 这里才置位了才明确表示使用负载均衡锁 */
            ngx_use_accept_mutex   = 1;
            ngx_accept_mutex_held  = 0;
            ngx_accept_mutex_delay = ecf->accept_mutex_delay;
    
        } else {
            /* 关闭负载均衡锁 */
            ngx_use_accept_mutex = 0;
        }
    
    #if (NGX_WIN32)
    
        /*
         * disable accept mutex on win32 as it may cause deadlock if
         * grabbed by a process which can't accept connections
         */
    
        ngx_use_accept_mutex = 0;
    
    #endif
    
        /* 初始化红黑树实现的定时器 */
        ngx_queue_init(&ngx_posted_accept_events);
        ngx_queue_init(&ngx_posted_events);
    
        if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
            return NGX_ERROR;
        }
    
        /* 在调用 use 配置项指定的事件模块中,在 ngx_event_module_t 接口下,ngx_event_actions_t 
         * 中的 init 方法进行这个事件模块的初始化工作 */
        for (m = 0; cycle->modules[m]; m++) {
            if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
                continue;
            }
    
            if (cycle->modules[m]->ctx_index != ecf->use) {
                continue;
            }
    
            module = cycle->modules[m]->ctx;
    
            if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
                /* fatal */
                exit(2);
            }
    
            break;
        }
    
    #if !(NGX_WIN32)
    
        /* 如果 nginx.conf 配置文件中设置了 timer_resolution 配置项,即表明需要控制时间
         * 精度,这时会调用 setitimer 方法,设置时间间隔为 timer_resolution 毫秒来回调
         * ngx_timer_signal_handler 方法 */
        if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
            struct sigaction  sa;
            struct itimerval  itv;
    
            ngx_memzero(&sa, sizeof(struct sigaction));
            /* 在 ngx_timer_signal_handler 方法中仅是对全局变量 ngx_event_timer_alarm 
             * 置 1,表示需要更新时间,在 ngx_event_actions_t 的 process_events 方法中,
             * 每一个事件驱动模块都需要在 ngx_event_timer_alarm 为 1 时调用 
             * ngx_time_update 方法更新系统时间,在更新系统时间结束后需要将 
             * ngx_event_timer_alarm 置为 0 */
            sa.sa_handler = ngx_timer_signal_handler;
            sigemptyset(&sa.sa_mask);
    
            if (sigaction(SIGALRM, &sa, NULL) == -1) {
                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                              "sigaction(SIGALRM) failed");
                return NGX_ERROR;
            }
    
            itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
            itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
            itv.it_value.tv_sec = ngx_timer_resolution / 1000;
            itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;
    
            if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                              "setitimer() failed");
            }
        }
    
        if (ngx_event_flags & NGX_USE_FD_EVENT) {
            struct rlimit  rlmt;
    
            if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                              "getrlimit(RLIMIT_NOFILE) failed");
                return NGX_ERROR;
            }
    
            cycle->files_n = (ngx_uint_t) rlmt.rlim_cur;
    
            cycle->files = ngx_calloc(sizeof(ngx_connection_t *) * cycle->files_n,
                                      cycle->log);
            if (cycle->files == NULL) {
                return NGX_ERROR;
            }
        }
    
    #else
    
        if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
            ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
                          "the "timer_resolution" directive is not supported "
                          "with the configured event method, ignored");
            ngx_timer_resolution = 0;
        }
    
    #endif
    
        /* 预分配 ngx_connection_t 数组作为连接池,同时将 ngx_cycle_t 结构体中的
         * connections 成员指向该数组。数组的个数为 nginx.conf 配置文件中 
         * worker_connections 中配置的连接数 */
        cycle->connections =
            ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
        if (cycle->connections == NULL) {
            return NGX_ERROR;
        }
    
        c = cycle->connections;
    
        /* 预分配 ngx_event_t 事件数组作为读事件池,同时将 ngx_cycle_t 结构体中的 
         * read_events 成员指向该数组。数组的个数为 nginx.conf 配置文件中 
         * worker_connections 里配置的连接数 */
        cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
                                       cycle->log);
        if (cycle->read_events == NULL) {
            return NGX_ERROR;
        }
    
        rev = cycle->read_events;
        for (i = 0; i < cycle->connection_n; i++) {
            rev[i].closed = 1;
            rev[i].instance = 1;
        }
    
        /* 预分配 ngx_event_t 事件数组作为写事件池,同时将 ngx_cycle_t 结构体中的 
         * write_events 成员指向该数组。数组的个数为 nginx.conf 配置文件中 
         * worker_connections 里配置的连接数 */
        cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
                                        cycle->log);
        if (cycle->write_events == NULL) {
            return NGX_ERROR;
        }
    
        wev = cycle->write_events;
        /* 将所有写事件池的写事件置为关闭状态,即未使用 */
        for (i = 0; i < cycle->connection_n; i++) {
            wev[i].closed = 1;
        }
    
        i = cycle->connection_n;
        next = NULL;
    
        /* 按照序号,将上述 3 个数组相应的读/写事件设置到每一个 ngx_connection_t 连接
         * 对象中,同时把这些连接以 ngx_connection_t 中的 data 成员作为 next 指针串联
         * 成链表,为下一步设置空闲连接链表做好准备 */
        do {
            i--;
    
            c[i].data  = next;
            c[i].read  = &cycle->read_events[i];
            c[i].write = &cycle->write_events[i];
            c[i].fd    = (ngx_socket_t) -1;
    
            next = &c[i];
        } while (i);
    
        /* 将 ngx_cycle_t 结构体中的空闲连接链表 free_connections 指向 connections 数组
         * 的第 1 个元素,也就是上一步所有 ngx_connection_t 连接通过 data 成员组成的
         * 单链表的首部 */
        cycle->free_connections = next;
        cycle->free_connection_n = cycle->connection_n;
    
        /* for each listening socket */
    
        /* 在刚刚建立好的连接池中,为所有 ngx_listening_t 监听对象中 connections 成员
         * 分配连接,同时对监听端口的读事件设置处理方法为 ngx_event_accept,也就是说,
         * 有新连接事件时将调用 ngx_event_accept 方法建立新连接 */
        ls = cycle->listening.elts;
        for (i = 0; i < cycle->listening.nelts; i++) {
    
    #if (NGX_HAVE_REUSEPORT)
            if (ls[i].reuseport && ls[i].worker != ngx_worker) {
                continue;
            }
    #endif
    
            c = ngx_get_connection(ls[i].fd, cycle->log);
    
            if (c == NULL) {
                return NGX_ERROR;
            }
    
            c->type = ls[i].type;
            c->log  = &ls[i].log;
    
            c->listening = &ls[i];
            ls[i].connection = c;
    
            rev = c->read;
    
            rev->log    = c->log;
            rev->accept = 1;
    
    #if (NGX_HAVE_DEFERRED_ACCEPT)
            rev->deferred_accept = ls[i].deferred_accept;
    #endif
    
            if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
                if (ls[i].previous) {
    
                    /*
                     * delete the old accept events that were bound to
                     * the old cycle read events array
                     */
    
                    old = ls[i].previous->connection;
    
                    if (ngx_del_event(old->read, NGX_READ_EVENT, NGX_CLOSE_EVENT)
                        == NGX_ERROR)
                    {
                        return NGX_ERROR;
                    }
    
                    old->fd = (ngx_socket_t) -1;
                }
            }
    
    #if (NGX_WIN32)
    
            if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
                ngx_iocp_conf_t  *iocpcf;
    
                rev->handler = ngx_event_acceptex;
    
                if (ngx_use_accept_mutex) {
                    continue;
                }
    
                if (ngx_add_event(rev, 0, NGX_IOCP_ACCEPT) == NGX_ERROR) {
                    return NGX_ERROR;
                }
    
                ls[i].log.handler = ngx_acceptex_log_error;
    
                iocpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module);
                if (ngx_event_post_acceptex(&ls[i], iocpcf->post_acceptex)
                    == NGX_ERROR)
                {
                    return NGX_ERROR;
                }
    
            } else {
                rev->handler = ngx_event_accept;
    
                if (ngx_use_accept_mutex) {
                    continue;
                }
    
                if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
                    return NGX_ERROR;
                }
            }
    
    #else
            /* 设置读事件的回调方法 */
            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
    
            if (ngx_use_accept_mutex) {
                continue;
            }
    
    #if (NGX_HAVE_EPOLLEXCLUSIVE)
    
            if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
                && ccf->worker_processes > 1)
            {
                if (ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)
                    == NGX_ERROR)
                {
                    return NGX_ERROR;
                }
    
                continue;
            }
    
    #endif
            
            /* 将监听对象连接的读事件添加到事件驱动模块中,这样,epoll 等事件模块
             * 就开始检测监听服务,并开始向用户提供服务了 */
            if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
                return NGX_ERROR;
            }
    
    #endif
    
        }
    
        return NGX_OK;
    }
    
  • 相关阅读:
    JavaScript小技巧总结
    Table边框使用总结 ,只显示你要显示的边框
    连续字符换行及单行溢出点点点显示
    对ThreadLocal的理解个人
    Linux面试题答案解析
    com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class
    21道 Redis 常见面试题,必须掌握!
    MyBatis中的#与$
    Linux下zookeeper的搭建
    web.xml加载顺序与过程
  • 原文地址:https://www.cnblogs.com/jimodetiantang/p/8948063.html
Copyright © 2020-2023  润新知