nginx源码分析(6)——建立连接

来源:互联网 发布:淘宝网儿童卡通抱枕 编辑:程序博客网 时间:2024/05/29 18:47

        对于web server来说,必须能够监听到客户端的连接才能与之通信,这篇文章就看一下nginx是如何实现连接的建立。监听到新的连接实际上就是监听socket上的读事件,此时监听socket的已完成连接队列是非空的,可以非阻塞的调用accpet获取新到的连接。在nginx中每个socket都会被封装成一个连接结构,就是ngx_connection_t类型。每个ngx_connection_t结构具有读写事件read和write,它们是ngx_event_t类型的,有一个handler回调函数指针,在发生读写事件时被调用。在监听socket初始化一文中介绍了socket的初始化,主要是获取监听地址、端口等信息。在事件模型中介绍了nginx事件模型的初始化,在ngx_event_process_init函数中为每个监听socket分配连接,并将这些连接的read的handler初始化为ngx_event_accept,也就是说说在监听到连接时会调用,用于初始化连接等,最后将其添加到事件循环中。

        在事件循环中,已经介绍过为了防止惊群(新到的一个连接会唤醒所有阻塞的worker进程),只有获取accept锁的worker进程才能accept新的连接,接下来才会去调用ngx_event_accept函数处理。下面就看看具体过程。

1. ngx_event_accept

    ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);    if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {        ev->available = 1;    } else if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {        ev->available = ecf->multi_accept;    }
        初始化event的available属性,表示事件发生。

    lc = ev->data;    ls = lc->listening;    ev->ready = 0;    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,                   "accept on %V, ready: %d", &ls->addr_text, ev->available);
        ngx_event_t的data属性是该事件所在的连接,对于监听socket的连接,可以通过listening属性获取对应的监听socket(ngx_listening_t)。接下来的while循环用于迭代ev->available次数,获取对应的连接。在while循环一开始的部分就是调用accept获取连接socket。

    ngx_accept_disabled = ngx_cycle->connection_n / 8                              - ngx_cycle->free_connection_n;
        在介绍事件模型时,提到过ngx_accept_disabled主要用于实现worker进程的简单的负载均衡,在一个worker进程的空闲连接的个数小于连接池大小的1/8时,该进程会放弃竞争accept锁,ngx_accept_disabled在ngx_process_events_and_timers函数中使用。
        c = ngx_get_connection(s, ev->log);        if (c == NULL) {            if (ngx_close_socket(s) == -1) {                ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,                              ngx_close_socket_n " failed");            }            return;        }
        s是连接socket的文件描述符,从连接池中获取一个连接并分配给该socket。

        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;        }
        连接的sockaddr存放客户端socket地址,这里为其分配空间。然后为连接的log结构分配空间。

        if (ngx_inherited_nonblocking) {            if (ngx_event_flags & NGX_USE_AIO_EVENT) {                if (ngx_blocking(s) == -1) {                    ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,                                  ngx_blocking_n " failed");                    ngx_close_accepted_connection(c);                    return;                }            }        } else {        // 设置成非阻塞            if (!(ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT))) {                if (ngx_nonblocking(s) == -1) {                    ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,                                  ngx_nonblocking_n " failed");                    ngx_close_accepted_connection(c);                    return;                }            }        }
        在使用aio时采用阻塞方式,其他模式(epoll、select等)使用非阻塞。

        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->unexpected_eof = 1;
        对新建的连接的一些属性初始化,包括接收、发送的函数,日志、监听socket等。接下来,初始化连接的事件部分。

        rev = c->read;        wev = c->write;        wev->ready = 1;        if (ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT)) {            /* rtsig, aio, iocp */            rev->ready = 1;        }        if (ev->deferred_accept) {            rev->ready = 1;#if (NGX_HAVE_KQUEUE)            rev->available = 1;#endif        }        rev->log = log;        wev->log = log;
        设置读写事件的ready属性,该属性表示该事件已经就绪,可以被触发。

        c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
        ngx_connection_counter是nginx的连接计数器,防止共享内存中,初始化是在event module的module init回调函数中调用,具体就是ngx_event_module_init函数。连接的number字段很显然表示该连接的序号。
        if (ls->addr_ntop) {            c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);            if (c->addr_text.data == NULL) {                ngx_close_accepted_connection(c);                return;            }            c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->addr_text.data,                                             ls->addr_text_max_len, 0);            if (c->addr_text.len == 0) {                ngx_close_accepted_connection(c);                return;            }        }
        这段代码将二进制的地址转换为文本格式,并赋值给连接的addr_text属性。

        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;            }        }
        调用ngx_add_conn将新建的连接加入nginx的事件循环。在使用epoll时,实际上会调用ngx_epoll_add_connection函数,最终调用epoll_ctl添加事件,这样后续就会监听到来自该socket的数据。

        log->data = NULL;        log->handler = NULL;        /**         * 调用监听socket的初始化函数ngx_http_init_connection(http/ngx_http_request.c),这个handler         * 是在ngx_http_add_listening函数中赋值的         */        ls->handler(c);
        ls是监听socket(ngx_listening_t),handler在accept到新连接时调用,注释已经说的很清楚,就是调用ngx_http_init_connection完成连接的初始化,其中最主要的就是为读事件设置handler,下面看一下这个函数。

2. ngx_http_init_connection

    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 = "reading client request line";    c->log_error = NGX_ERROR_INFO;
        初始化log相关属性。
    rev = c->read;    rev->handler = ngx_http_init_request;    c->write->handler = ngx_http_empty_handler;
        这段代码是最核心的,将读事件的handler设置为ngx_http_init_request,在客户端向服务器发送数据时会被调用,用于初始化并处理客户端请求。在后面介绍ngxin请求处理会详细介绍。

    if (rev->ready) {        /* the deferred accept(), rtsig, aio, iocp */        if (ngx_use_accept_mutex) {            ngx_post_event(rev, &ngx_posted_events);            return;        }        ngx_http_init_request(rev);        return;    }
        按照注释的描述这段代码在使用deferred accept、rtsig、aio和iocp时调用,在ngx_event_accept中只有在这几种情况下才会将读事件的ready置为1。

    ngx_add_timer(rev, c->listening->post_accept_timeout);    /*     * 将新建的连接的描述符添加到事件循环     */    if (ngx_handle_read_event(rev, 0) != NGX_OK) {#if (NGX_STAT_STUB)        (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);#endif        ngx_http_close_connection(c);        return;    }
        设置timer,最后将新建的socket描述符添加到事件循环,但是在使用epoll时添加到事件循环的工作已经在ngx_add_conn中完成,所以这个函数不会做任何操作。

        上面就是nginx建立连接的处理过程,主要是初始化连接的属性,并设置了读事件的handler,用于处理请求,下面一篇介绍nginx的请求处理。




原创粉丝点击