nginx http框架流程(1) - 新连接的建立

2018-07-28 10:50:06

当 epoll 检测到连接事件时,会调用 event_accept,最后会调用 ngx_http_init_connection,它是 http 处理的起点,此时连接已经建立,socket是可写的,但不一定是可读(可能数据还没从客户端发过来),所以要做的是关注 socket 上的读事件。

    ngx_uint_t              i;
    ngx_event_t            *rev;
    struct sockaddr_in     *sin;
    ngx_http_port_t        *port;
    ngx_http_in_addr_t     *addr;
    ngx_http_log_ctx_t     *ctx;
    ngx_http_connection_t  *hc;

    struct sockaddr_in6    *sin6;
    ngx_http_in6_addr_t    *addr6;
//创建连接的相关配置信息    
    hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
    // 之后在create_request里使用
    c->data = hc;
// 取监听同一端口的server信息
    port = c->listening->servers;

    // 一个端口对应多个地址的情况
    if (port->naddrs > 1) {
        //在数组里找到对应的一个地址
    }else{
        addr = port->addrs;
        hc->addr_conf = &addr[0].conf;  //取对应地址的配置信息
    }

    //就是端口所在的 server 的配置数组
    hc->conf_ctx = hc->addr_conf->default_server->ctx;

确定了 server之后,,Nginx 就准备接收客户端发来的 http 请求数据,为此需要在事件机制里监控 socket的读事件,设置函数为 ngx_http_wait_request_handler 因为在接收请求头数据之前不可能发送 HTTP 响应数据,所以写事件不处理,设置为空函数 ngx_http_empty_handler。

// 连接的读事件,此时是已经发生连接,即将读数据
    rev = c->read;

    // 处理读事件,读取请求头
    rev->handler = ngx_http_wait_request_handler;

    // 暂时不处理写事件
    c->write->handler = ngx_http_empty_handler;

如果在监听端口上配置了 devered 参数,那么建立连接时 socket 上已经有了可读数据,可以直接处理。否则就要设置超时时间,并且把读事件加入到 epoll 监控,等待客户端发送数据,再次进入 http 处理机制。

TCP_DEFER_ACCEPT 意思是当客户端和服务端三次握手后,服务端不会立马 accept,而是等待有第一个实际数据发送过来时才accept,所以当设置了 deferred,那么只要 accept 过来了,那么肯定可以读,因为里面已经有数据了。

//使用了deferred才是ready
// ngx_event_accept里设置
// 为了提高nginx的性能,减少epoll调用,应该设置deferred
if (rev->ready) {

    // 直接处理请求,即调用ngx_http_wait_request_handler
    rev->handler(rev);
    return;
}
// 虽然建立了连接,但暂时没有数据可读,ready=0
    // 加一个超时事件,等待读事件发生
    ngx_add_timer(rev, c->listening->post_accept_timeout);

    // 把读事件加入epoll,当socket有数据可读时就调用ngx_http_wait_request_handler
    // 因为事件加入了定时器,超时时也会调用ngx_http_wait_request_handler
    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        // 调用ngx_close_connection
        // 释放连接,加入空闲链表,可以再次使用
        // 销毁连接的内存池
        ngx_http_close_connection(c);
        return;
    }

流程图大致如下


一旦有数据可读,nginx 就会调用 ngx_http_wait_request_handler

// 从事件的data获得连接对象
    c = rev->data;

    // 首先检查超时
    // 由定时器超时引发的,由ngx_event_expire_timers调用
    // 超时没有发送数据,关闭连接
    if (rev->timedout) {
        // 释放连接,加入空闲链表,可以再次使用
        // 销毁连接的内存池
        ngx_http_close_connection(c);
        return;
    }
    // 没有超时,检查连接是否被关闭了
    if (c->close) {
        ngx_http_close_connection(c);
        return;
    }
    // 连接对象里获取配置数组, 在ngx_http_init_connection里设置的
    // 重要的是conf_ctx,server的配置数组
    hc = c->data;
    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);

    // 配置的头缓冲区大小,默认1k
    // 如果头太大,或者配置的太小
    // nginx会再多分配内存,保证读取完数据
    size = cscf->client_header_buffer_size;

    // 这个缓冲区是给连接对象用的
    b = c->buffer;

    // 如果还没有创建缓冲区则创建
    // 第一次调用是没有,之后再调用就有了
    if (b == NULL) {
        b = ngx_create_temp_buf(c->pool, size);
        c->buffer = b;

    // 虽然已经有了buf结构,但没有关联的内存空间
    // 之前因为again被释放了,所以要重新分配内存
    // 见后面的recv判断
    } else if (b->start == NULL) {
        b->start = ngx_palloc(c->pool, size);
        // 这里pos==last==start,表示一个空的缓冲区
        b->pos = b->start;
        b->last = b->start;
        b->end = b->last + size;
    }

调用接收函数,b->last 是缓冲区的末位置,前面可能有数据。

n<0 出错, =0 连接关闭, >0 接收到数据大小。

n = c->recv(c, b->last, size);
// 如果返回NGX_AGAIN表示还没有数据
    // 就要再次加定时器防止超时,然后epoll等待下一次的读事件发生
    if (n == NGX_AGAIN) {

        // 没设置超时就再来一次
        if (!rev->timer_set) {
            ngx_add_timer(rev, c->listening->post_accept_timeout);
            ngx_reusable_connection(c, 1);
        }

        // 把读事件加入epoll,当socket有数据可读时就调用ngx_http_wait_request_handler
        // 因为事件加入了定时器,超时时也会调用ngx_http_wait_request_handler
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_connection(c);
            return;
        }

        // 释放缓冲区,避免空闲连接占用内存
        // 这样,即使有大量的无数据连接,也不会占用很多的内存
        // 只有连接对象的内存消耗
        if (ngx_pfree(c->pool, b->start) == NGX_OK) {
            b->start = NULL;
        }

        // 读事件处理完成,因为没读到数据,等待下一次事件发生
        return;
    }
// 读数据出错了,直接关闭连接
    if (n == NGX_ERROR) {
        ngx_http_close_connection(c);
        return;
    }

    // 读到了0字节,即连接被客户端关闭
    if (n == 0) {
        ngx_http_close_connection(c);
        return;
    }

如果读取成功

// 真正读取了n个字节
    b->last += n;

    // 创建ngx_http_request_t对象,准备开始真正的处理请求
    c->data = ngx_http_create_request(c);

    // 读事件的handler改变,变成ngx_http_process_request_line
    // 之后再有数据来就换成ngx_http_process_request_line
    rev->handler = ngx_http_process_request_line;

    // 必须马上执行ngx_http_process_request_line
    // 否则因为et模式的特性,将无法再获得此事件
    ngx_http_process_request_line(rev);

流程图大致如下


备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/e1


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。