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

15 篇文章 3 订阅
11 篇文章 0 订阅

        对于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的请求处理。




评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值