关闭

nginx源码学习——Http连接对应的事件驱动模块

标签: 事件驱动nginx源码epollhttp
331人阅读 评论(0) 收藏 举报
分类:

事件驱动函数

事件驱动是nginx设计的核心,linux平台下,nginx会优先使用epoll进行事件处理。main—>master_process_cycle—>ngx_start_worker_process—>ngx_worker_process_cycle—>ngx_worker_process_init,ngx_process_events_and_timers。master进程中创建worker进程,之后在ngx_worker_process_cycle函数中初始化,并打开socketpair,加入epoll中。随后进入ngx_process_events_and_timers函数,等待http连接请求。nginx作为web服务器,性能要求很高,对最耗时的IO进行异步处理。注册到epoll上,作为一个个事件待触发。定义的事件处理函数如下:

ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */

    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
        NULL,                            /* process the changes */
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};

等待连接请求

worker进程启动后调用ngx_worker_process_init,初始化连接,监听80端口,之后不断调用ngx_process_events_and_timers 函数,等待http连接请求。

ngx_process_events_and_timers

    if (ngx_use_accept_mutex) {
        if (ngx_accept_disabled > 0) {
            ngx_accept_disabled--;
        //多进程情况下通过共享内存的互斥锁,同一时间只允许一个进程得到访问,解决惊群问题。
        } else {
        //得到互斥锁后,将对应端口的(Http默认为80端口)事件加入epoll,等待客户端连接请求,触发IO事件。
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return;
            }
        //多进程情况下,使用NGX_POST_EVENTS,将触发的事件放入队列中,而不是里面触发IO的回调函数
            if (ngx_accept_mutex_held) {
                flags |= NGX_POST_EVENTS;

            } else {
                if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
                {
                    timer = ngx_accept_mutex_delay;
                }
            }
        }
    }
   //调用的是ngx_epoll_process_events,执行的是epoll_wait,当有事件被触发时,将执行IO回调函数,或者将被触发的IO事件添加到链表。
    (void) ngx_process_events(cycle, timer, flags);
    //依次访问被触发的IO事件,执行回调
       if (ngx_posted_accept_events) {
        ngx_event_process_posted(cycle, &ngx_posted_accept_events);
    }
    //释放共享互斥锁
    if (ngx_accept_mutex_held) {
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }
    //处理超时事件
    if (delta) {
        ngx_event_expire_timers();
    }

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "posted events %p", ngx_posted_events);
    //处理被触发的IO事件回调
    if (ngx_posted_events) {
        if (ngx_threaded) {
            ngx_wakeup_worker_thread(cycle);

        } else {
            ngx_event_process_posted(cycle, &ngx_posted_events);
        }
    }

之所以要在多进程时,将触发的IO事件先添加链表,而不是立马执行回调。是因为ngx_trylock_accept_mutex函数会抢占锁,这样其他进程就必须等待,直到锁被释放才可以继续接受事件触发。而一般的IO回调函数都比较耗时,一旦多个事件同时触发,等待周期会比较长。

(void) ngx_process_events(cycle, timer, flags)

    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;
        //多进程情况下不执行handler,而是添加到链表中
        if (flags & NGX_POST_EVENTS) {
        queue = (ngx_event_t **) (rev->accept ?&ngx_posted_accept_events : &ngx_posted_events);

        ngx_locked_post_event(rev, queue);

        } else {
                rev->handler(rev);
        }
      }

连接建立

当Http客户端发来连接请求后,触发读事件的Handler函数,在初始化时,设定这个函数为ngx_event_accept。这个函数里调用accept,建立TCP通信。

ngx_event_accept

    lc = ev->data;
    ls = lc->listening;
    ev->ready = 0;

    do {
        socklen = NGX_SOCKADDRLEN;
        //accept等待连接建立
        s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
        //当一个进程的free连接数占连接总数的八分之一时,不在接收新的连接请求
        ngx_accept_disabled = ngx_cycle->connection_n / 8
                              - ngx_cycle->free_connection_n;
        //获取一个可用的连接,并初始化
        c = ngx_get_connection(s, ev->log);
        c->pool = ngx_create_pool(ls->pool_size, ev->log);
        if (c->pool == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }

        c->sockaddr = ngx_palloc(c->pool, socklen);
        if (c->sockaddr == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }

        ngx_memcpy(c->sockaddr, sa, socklen);

        log = ngx_palloc(c->pool, sizeof(ngx_log_t));
        if (log == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }

        *log = ls->log;
        //该Http连接所用的recv、send函数
        c->recv = ngx_recv;
        c->send = ngx_send;
        c->recv_chain = ngx_recv_chain;
        c->send_chain = ngx_send_chain;

        c->log = log;
        c->pool->log = log;

        c->socklen = socklen;
        c->listening = ls;
        c->local_sockaddr = ls->sockaddr;
        c->local_socklen = ls->socklen;

        c->unexpected_eof = 1;

        }

        rev = c->read;
        wev = c->write;

        wev->ready = 1;

        if (ev->deferred_accept) {
            rev->ready = 1;
        }

        rev->log = log;
        wev->log = log;

        /*
         * TODO: MT: - ngx_atomic_fetch_add()
         *             or protection by critical section or light mutex
         *
         * TODO: MP: - allocated in a shared memory
         *           - ngx_atomic_fetch_add()
         *             or protection by critical section or light mutex
         */
        //nginx的http连接数加1,连接数也是一个共享内存的全局变量
        c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
        //将新的connection加入到epoll中
        if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
            if (ngx_add_conn(c) == NGX_ERROR) {
                ngx_close_accepted_connection(c);
                return;
            }
        }

        log->data = NULL;
        log->handler = NULL;
        //调用监听套接字的回调函数,这里是ngx_http_init_connection
        ls->handler(c);

        if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
            ev->available--;
        }

    } while (ev->available);
}

listener的回调函数是http模块在初始化的时候被赋值的。

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_http_block—>ngx_http_optimize_servers—>ngx_http_init_listening—>ngx_http_add_listening。在ngx_http_add_listening中创建一个监听结构体ngx_listening_t,并初始化,设置回调句柄ls->handler = ngx_http_init_connection。
ngx_http_init_connection函数中又进一步设定connection读事件的回调函数为ngx_http_wait_request_handler,写事件的回调函数是ngx_http_empty_handler。并将读事件加入epoll监听队列,等待客户端的http请求报文。

处理请求

ngx_http_wait_request_handler函数分配内存,然后接收报文,并调用ngx_http_process_request_line解析报文的每一行信息,进行Http处理的每个环节。ngx_http_process_request_line—>ngx_http_handler—>ngx_http_core_run_phases。然后执行各个phases函数。

static void
ngx_http_wait_request_handler(ngx_event_t *rev)
{
    size_t                     size;
    ssize_t                    n;
    ngx_buf_t                 *b;
    ngx_connection_t          *c;
    ngx_http_connection_t     *hc;
    ngx_http_core_srv_conf_t  *cscf;

    c = rev->data;
    //连接响应超时,关闭连接
    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        ngx_http_close_connection(c);
        return;
    }

    if (c->close) {
        ngx_http_close_connection(c);
        return;
    }

    hc = c->data;
    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);

    size = cscf->client_header_buffer_size;

    b = c->buffer;
    //开辟一块缓存,用来接收Http报文
    if (b == NULL) {
        b = ngx_create_temp_buf(c->pool, size);
        if (b == NULL) {
            ngx_http_close_connection(c);
            return;
        }

        c->buffer = b;

    } else if (b->start == NULL) {

        b->start = ngx_palloc(c->pool, size);
        if (b->start == NULL) {
            ngx_http_close_connection(c);
            return;
        }

        b->pos = b->start;
        b->last = b->start;
        b->end = b->last + size;
    }
    //从网络缓存里读出http报文
    n = c->recv(c, b->last, size);

    if (n == NGX_ERROR) {
        ngx_http_close_connection(c);
        return;
    }

    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client closed connection");
        ngx_http_close_connection(c);
        return;
    }

    b->last += n;

    c->log->action = "reading client request line";

    ngx_reusable_connection(c, 0);

    c->data = ngx_http_create_request(c);
    if (c->data == NULL) {
        ngx_http_close_connection(c);
        return;
    }
    //这里将读事件的回调句柄改为ngx_http_process_request_line,是因为一个
    //http请求的报文可能会很长,可能会超出TCP的接收buffer,这样就会多次触发读
    //事件
    rev->handler = ngx_http_process_request_line;
    ngx_http_process_request_line(rev);
}

很多基于nginx的二次开发都是通过在Http报文里加入一些属性,然后开发对应的phrase方法,将phrase方法加入ngx_http_core_commands中。

static ngx_command_t  ngx_http_core_commands[] = {

    { ngx_string("variables_hash_max_size"),
      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      NGX_HTTP_MAIN_CONF_OFFSET,
      offsetof(ngx_http_core_main_conf_t, variables_hash_max_size),
      NULL },

    { ngx_string("variables_hash_bucket_size"),
      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      NGX_HTTP_MAIN_CONF_OFFSET,
      offsetof(ngx_http_core_main_conf_t, variables_hash_bucket_size),
      NULL },

    //... ...

连接关闭

客户端关闭http连接,调用ngx_http_finalize_request—>ngx_http_finalize_connection—>ngx_http_close_request—>ngx_http_free_request,ngx_http_close_connection。释放内存,关闭套接字,销毁内存池。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:14057次
    • 积分:354
    • 等级:
    • 排名:千里之外
    • 原创:17篇
    • 转载:19篇
    • 译文:0篇
    • 评论:2条
    最新评论