首页 > C/C++, Nginx > Nginx upstream原理分析【1】-无缓冲模式发送数据

Nginx upstream原理分析【1】-无缓冲模式发送数据

2013年5月18日 发表评论 阅读评论 10219次阅读    

前阵子学习了一下nginx 关于接收upstream的数据,然后发送给客户端的过程,总结一下。

下面的许多注释等是在upstream后面是FCGI协议而介绍的,比如nginx连接后端php-fpm的情况下。nginx接收到客户端的header数据后会调用各个处理过程,也就是ngx_http_core_content_phase,该过程会不断调用各个阶段(phrase)的回调函数,依次进行配置解析,重定向,访问控制,内容处理等过程。

在内容处理的回调ngx_http_core_content_phase()中,会调用ngx_http_fastcgi_handler,请求由后者接管。

nginx中关于upstream经常围绕在ngx_http_upstream_s上面,这上面包括各种缓冲区数据,回调等,所以先看一下ngx_http_upstream_s结构的内容,具体含义注释下面了。

struct ngx_http_upstream_s {//本结构体用来保存一个连接的upstream信息,包括各种需要upstream回调的函数等。
    ngx_http_upstream_handler_pt     read_event_handler; //ngx_http_upstream_process_header
    ngx_http_upstream_handler_pt     write_event_handler;// ngx_http_upstream_send_request_handler

    ngx_peer_connection_t            peer;

    ngx_event_pipe_t                *pipe;

    ngx_chain_t                     *request_bufs;//客户端发送过来的数据body部分,在ngx_http_upstream_init_request设置为客户端发送的HTTP BODY
    //也可能是代表要发送给后端的数据链表结构,比如ngx_http_proxy_create_request会这么放的。比如是FCGI结构数据,或者Proxy结构等。

    ngx_output_chain_ctx_t           output;//输出数据的结构,里面存有要发送的数据,以及发送的output_filter指针
    ngx_chain_writer_ctx_t           writer;//参考ngx_chain_writer,里面会将输出buf一个个连接到这里。

    ngx_http_upstream_conf_t        *conf;//为u->conf = &flcf->upstream;

    ngx_http_upstream_headers_in_t   headers_in;//存放从上游返回的头部信息,

    ngx_http_upstream_resolved_t    *resolved;//解析出来的fastcgi_pass   127.0.0.1:9000;后面的字符串内容,可能有变量嘛。

    ngx_buf_t                        buffer;///读取上游返回的数据的缓冲区,也就是proxy,FCGI返回的数据。这里面有http头部,也可能有body部分。
    									//其body部分会跟event_pipe_t的preread_bufs结构对应起来。就是预读的buf,其实是i不小心读到的。
    size_t                           length;//要发送给客户端的数据大小

    ngx_chain_t                     *out_bufs;//这个是要发送给客户端的数据链接表?
    ngx_chain_t                     *busy_bufs;//调用了ngx_http_output_filter,并将out_bufs的链表数据移动到这里,待发送完毕后,会移动到free_bufs
    ngx_chain_t                     *free_bufs;//空闲的缓冲区。可以分配

    ngx_int_t                      (*input_filter_init)(void *data);//进行初始化,没什么用,memcache设置为ngx_http_memcached_filter_init
    ngx_int_t                      (*input_filter)(void *data, ssize_t bytes);//ngx_http_upstream_non_buffered_filter,ngx_http_memcached_filter等。
    void                            *input_filter_ctx;//指向所属的请求等上下文

#if (NGX_HTTP_CACHE)
    ngx_int_t                      (*create_key)(ngx_http_request_t *r);
#endif
	//下面的upstream回调指针是各个模块设置的,比如ngx_http_fastcgi_handler里面设置了fcgi的相关回调函数。
    ngx_int_t                      (*create_request)(ngx_http_request_t *r);//生成发送到上游服务器的请求缓冲(或者一条缓冲链)
    ngx_int_t                      (*reinit_request)(ngx_http_request_t *r);//在后端服务器被重置的情况下(在create_request被第二次调用之前)被调用
    ngx_int_t                      (*process_header)(ngx_http_request_t *r);//处理上游服务器回复的第一个bit,时常是保存一个指向上游回复负载的指针
    void                           (*abort_request)(ngx_http_request_t *r);//在客户端放弃请求的时候被调用
    void                           (*finalize_request)(ngx_http_request_t *r,//在Nginx完成从上游服务器读入回复以后被调用
                                         ngx_int_t rc);
    ngx_int_t                      (*rewrite_redirect)(ngx_http_request_t *r,
                                         ngx_table_elt_t *h, size_t prefix);

    ngx_msec_t                       timeout;

    ngx_http_upstream_state_t       *state;//当前的状态

    ngx_str_t                        method;
    ngx_str_t                        schema;
    ngx_str_t                        uri;

    ngx_http_cleanup_pt             *cleanup;//ngx_http_upstream_cleanup

    unsigned                         store:1;
    unsigned                         cacheable:1;
    unsigned                         accel:1;
    unsigned                         ssl:1;
#if (NGX_HTTP_CACHE)
    unsigned                         cache_status:3;
#endif

    unsigned                         buffering:1;//是否要buffer 后端的数据,如果要用event_pipe的方式发送数据

    unsigned                         request_sent:1;//是否已经将request_bufs的数据放入输出链表里面
    unsigned                         header_sent:1;//标记已经发送了头部字段。
};

ngx_http_core_content_phase函数就是主要的内容处理函数,upstream就是一个content内容处理模块,对于FCGI,它里面会立即调用自定义的内容处理回调:ngx_http_fastcgi_handler。

ngx_int_t
ngx_http_core_content_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
 size_t root;
 ngx_int_t rc;
 ngx_str_t path;

 if (r->content_handler) {
//如果有content_handler,就直接调用就行了.比如如果是FCGI,在遇到配置fastcgi_pass 127.0.0.1:8777;的时候
//会调用ngx_http_fastcgi_pass函数,注册本location的处理hander为ngx_http_fastcgi_handler。
//从而在ngx_http_update_location_config里面会更新content_handler指针为当前loc所对应的指针。
 r->write_event_handler = ngx_http_request_empty_handler;
 ngx_http_finalize_request(r, r->content_handler(r));
 return NGX_OK;
 }

 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "content phase: %ui", r->phase_handler);
 rc = ph->handler(r);//这个是什么?
 if (rc != NGX_DECLINED) {
 ngx_http_finalize_request(r, rc);
 return NGX_OK;
 }
}

在handler后续的过程中主要包括:
初始化: ngx_http_fastcgi_handler 函数接管请求后,会调用ngx_http_upstream_create创建一个upstream结构,然后调用ngx_http_read_client_request_body读取客户端发送过来的 HTTP body,如果有的话。然后调用ngx_http_upstream_init去初始化一些数据结构;
FCGI数据准备:之后在ngx_http_upstream_init_request函数里面会调用u->create_request(r)函数,将客户端发送过来的HTTP格式数据解析为FCGI格式的包,存放在u->request_bufs链接表里面。如果是Proxy模块,ngx_http_proxy_create_request组件反向代理的头部啥的,放到u->request_bufs里面。
连接发送数据:

ngx_http_upstream_connect()函数连接后端的php,其中可以有RR的,或者根据IP等进行哈希的策略选取一个SOCK去连接PHP端;

ngx_http_upstream_send_request()函数调用ngx_output_chain将刚刚准备的FCGI协议数据发送给PHP。

到目前为止,客户端发送过来的数据终于发送给了PHP了,对于proxy也类似,只是准备数据的过程不一样而已。其他部分大都一样的。啰嗦了这么多,到本文的重点了: 读取后端PHP数据,发送给客户端


数据发送给后端PHP数据后,数据读取就由ngx_http_upstream_process_header()函数来完成了。首先做一下超时检查,连接状态检查,然后分配了u->buffer用来存放FCGI数据。ngx_list_init(&u->headers_in.headers,···)初始化headers_in结构,这个结构用来存放PHP返回的HTTP同步数据,类似于headers_out。

然后就循环调用ngx_unix_recv函数,读取PHP发送的数据存放在u->buffer.last开始的地方,知道不可读为止。读完后就会调用 u->process_header(r);函数进行数据解析,解析FCGI数据,这个函数对于FCGI是ngx_http_fastcgi_process_header()。到这个时候,其实PHP返回的数据不一定都读取到了,只是抱着读取的数据的HEADER部分就行。解析完后就调用到ngx_http_upstream_process_headers()函数,这个函数没干什么事情,只是将HEADER放入r->headers_out里面,然后循环调用每个头的copy_handler,这个可以参考ngx_http_upstream_headers_in全局变量。头部信息处理完后如果没有子请求的话,直接调用ngx_http_upstream_send_response()函数完成,这个函数是我们的重点,进行数据发送给客户端的。下面先看一下ngx_http_upstream_process_header的代码,有删节:

static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u)
{//读取FCGI头部数据,或者proxy头部数据。ngx_http_upstream_send_request发送完数据后,
//会调用这里,或者有可写事件的时候会调用这里。
    ssize_t            n;
    ngx_int_t          rc;
    ngx_connection_t  *c;
    c = u->peer.connection;
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process header");
    c->log->action = "reading response header from upstream";
    if (c->read->timedout) {//读超时了,轮询下一个。错误信息应该已经打印了
        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
        return;
    }
	//我已发送请求,但连接出问题了
    if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {
        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
        return;
    }
    if (u->buffer.start == NULL) {//分配一块缓存,用来存放接受回来的数据。
        u->buffer.start = ngx_palloc(r->pool, u->conf->buffer_size);
        if (u->buffer.start == NULL) {
            ngx_http_upstream_finalize_request(r, u,  NGX_HTTP_INTERNAL_SERVER_ERROR);
            return;
        }
        u->buffer.pos = u->buffer.start;
        u->buffer.last = u->buffer.start;
        u->buffer.end = u->buffer.start + u->conf->buffer_size;
        u->buffer.temporary = 1;
        u->buffer.tag = u->output.tag;
		//初始化headers_in存放头部信息,后端FCGI,proxy解析后的HTTP头部将放入这里
        if (ngx_list_init(&u->headers_in.headers, r->pool, 8, sizeof(ngx_table_elt_t))
            != NGX_OK){
            ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return;
        }
#if (NGX_HTTP_CACHE)
        if (r->cache) {
            u->buffer.pos += r->cache->header_start;
            u->buffer.last = u->buffer.pos;
        }
#endif
    }

    for ( ;; ) {//不断调recv读取数据,如果没有了,就先返回
    //recv 为 ngx_unix_recv,读取数据放在u->buffer.last的位置,返回读到的大小。
        n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last);
        if (n == NGX_AGAIN) {//还没有读完,还需要关注这个事情。
            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {//参数为0,那就不变,保持原样
                ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }
            return;
        }
        if (n == 0) {
            ngx_log_error(NGX_LOG_ERR, c->log, 0,  "upstream prematurely closed connection");
        }
        if (n == NGX_ERROR || n == 0) {//失败重试
            ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
            return;
        }
        u->buffer.last += n;//成功,移动读取到的字节数指到最后面
        rc = u->process_header(r);//ngx_http_fastcgi_process_header等,进行数据处理,比如后端返回的数据头部解析,body读取等。
        //注意这个函数执行完成后,BODY不一定全部读取成功了。这个函数类似插件,有FCGI,proxy插件,将其FCGI的包数据解析,
        //解析出HTTP头部,放入headers_in里面,当头部解析碰到\r\n\r\n的时候,也就是空行,结束返回,暂时不读取BODY了。
        //因为我们必须处理头部才知道到底有多少BODY,还有没有FCGI_STDOUT。
        if (rc == NGX_AGAIN) {
            if (u->buffer.pos == u->buffer.end) {
                ngx_log_error(NGX_LOG_ERR, c->log, 0,"upstream sent too big header");
                ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);
                return;
            }
            continue;//继续。请求的HTTP,FCGI头部没有处理完毕。
        }
        break;//到这里说明请求的头部已经解析完毕了。下面只剩下body了,BODY不急
    }
    if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {//头部格式错误。尝试下一个服务器。
        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);
        return;
    }
    if (rc == NGX_ERROR) {//出错,结束请求。
        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }
    /* rc == NGX_OK */
    if (u->headers_in.status_n > NGX_HTTP_SPECIAL_RESPONSE) {//如果状态码大于300
        if (r->subrequest_in_memory) {
            u->buffer.last = u->buffer.pos;
        }
        if (ngx_http_upstream_test_next(r, u) == NGX_OK) {
            return;
        }
        if (ngx_http_upstream_intercept_errors(r, u) == NGX_OK) {
            return;
        }
    }
	//到这里,FCGI等格式的数据已经解析为标准HTTP的表示形式了(除了BODY),所以可以进行upstream的process_headers。
	//上面的 u->process_header(r)已经进行FCGI等格式的解析了。下面将头部数据拷贝到headers_out.headers数组中。
    if (ngx_http_upstream_process_headers(r, u) != NGX_OK) {
        return;//解析请求的头部字段。每行HEADER回调其copy_handler,然后拷贝一下状态码等。
    }
    if (!r->subrequest_in_memory) {//如果没有子请求了,那就直接发送响应给客户端吧。
        ngx_http_upstream_send_response(r, u);//给客户端发送响应,里面会处理header,body分开发送的情况的
        return;
    }
	//如果还有子请求的话。子请求不是标准HTTP。
    /* subrequest content in memory */
    //····
    u->read_event_handler = ngx_http_upstream_process_body_in_memory;//设置body部分的读事件回调。
    ngx_http_upstream_process_body_in_memory(r, u);
}

发送数据给客户端的过程比较复杂一步步来看,这是ngx_http_upstream_send_response函数的工作。其前面部分比较简单,首先将header发送给客户端,然后判断一些标志,如下:

static void ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u)
{//发送请求数据给客户端。里面会处理header,body分开发送的情况的
int                        tcp_nodelay;
ssize_t                    n;
ngx_int_t                  rc;
ngx_event_pipe_t          *p;
ngx_connection_t          *c;
ngx_http_core_loc_conf_t  *clcf;
	//先发header,再发body
rc = ngx_http_send_header(r);//调用每一个filter过滤,处理头部数据。最后将数据发送给客户端。调用ngx_http_top_header_filter
if (rc == NGX_ERROR || rc > NGX_OK || r->post_action) {
ngx_http_upstream_finalize_request(r, u, rc);
return;
}
c = r->connection;
if (r->header_only) {//如果只需要发送头部数据,比如客户端用curl -I 访问的。返回204状态码即可。
if (u->cacheable || u->store) {
if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) {//关闭TCP的写数据流
ngx_connection_error(c, ngx_socket_errno, ngx_shutdown_socket_n " failed");
}
r->read_event_handler = ngx_http_request_empty_handler;//设置读写事件回调为空函数,这样就不会care数据读写了。
r->write_event_handler = ngx_http_request_empty_handler;
c->error = 1;//标记为1,一会就会关闭连接的。
} else {
ngx_http_upstream_finalize_request(r, u, rc);
return;
}
}
u->header_sent = 1;//标记已经发送了头部字段,至少是已经挂载出去,经过了filter了。
if (r->request_body && r->request_body->temp_file) {//删除客户端发送的数据局体
ngx_pool_run_cleanup_file(r->pool, r->request_body->temp_file->file.fd);
r->request_body->temp_file->file.fd = NGX_INVALID_FILE;
}

ngx_http_send_header函数比较简单,他就调用了ngx_http_top_header_filter过滤函数对头部数据进行过滤,第一个是ngx_http_not_modified_header_filter,后面一个个往后掉用。每个函数独立一个文件,那里面有个静态变量记录它的上一个filter是谁。最后一个是ngx_http_header_filter_module模块里面的发送模块。

nginx对于后端返回的数据处理有2种模式:buffering 和无buffering,也就是是接一点数据就发送一点数据,还是要带缓存的接收一部分数据后,达到一定程度才发送数据。我们先来看简单的无缓冲的模式。

注意这个带缓存的意思不是说nginx会读取完upstream的所有数据才发送,而是读取完一块由fastcgi_buffers等参数指定的buf后。

无缓存模式:
无缓冲模式u->buffering成员为0,当然对于FCGI会写死为1的,因为FCGI数据协议是一个个包的发送,每个包的前面带有length,所以不需要进行流式传输,比如proxy。ngx_http_upstream_send_response对于无缓冲模式的处理包括设置upstream和客户端连接的读写回调函数,调用ngx_http_upstream_process_non_buffered_downstream发送数据给客户端,ngx_http_upstream_process_non_buffered_upstream接收upstream的数据,代码如下:

    if (!u->buffering) {//FCGI写死为1.因为FCGI是包式的传输。非流式,不能接一点,发一点。在ngx_http_fastcgi_handler里面设置为1了。
//buffering指nginx 会先buffer后端FCGI发过来的数据,然后一次发送给客户端。
//默认这个是打开的。也就是nginx会buf住upstream发送的数据。这样效率会更高。
        if (u->input_filter == NULL) {//如果input_filter为空,则设置默认的filter,然后准备发送数据到客户端。然后试着读读FCGI
            u->input_filter_init = ngx_http_upstream_non_buffered_filter_init;//实际上为空函数
            //ngx_http_upstream_non_buffered_filter将u->buffer.last - u->buffer.pos之间的数据放到u->out_bufs发送缓冲去链表里面。
            u->input_filter = ngx_http_upstream_non_buffered_filter;//一般就设置为这个默认的,memcache为ngx_http_memcached_filter
            u->input_filter_ctx = r;
        }
		//设置upstream的读事件回调,设置客户端连接的写事件回调。
        u->read_event_handler = ngx_http_upstream_process_non_buffered_upstream;
        r->write_event_handler = ngx_http_upstream_process_non_buffered_downstream;//调用过滤模块一个个过滤body,最终发送出去。
        r->limit_rate = 0;
        if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) {//调用input filter 初始化函数,没做什么事情
            ngx_http_upstream_finalize_request(r, u, 0);
            return;
        }
        if (clcf->tcp_nodelay && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) {
            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay");
            tcp_nodelay = 1;//打开nodelay,准备将数据完全发送出去
            if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, (const void *) &tcp_nodelay, sizeof(int)) == -1) {
                ngx_connection_error(c, ngx_socket_errno,  "setsockopt(TCP_NODELAY) failed");
                ngx_http_upstream_finalize_request(r, u, 0);
                return;
            }
            c->tcp_nodelay = NGX_TCP_NODELAY_SET;
        }
        n = u->buffer.last - u->buffer.pos;//得到将要发送的数据的大小,每次有多少就发送多少。不等待upstream了
        if (n) {
            u->buffer.last = u->buffer.pos;//将last指向为当前的pos,那post-last之前的数据没了,不过上面有个n记着了的。
            u->state->response_length += n;//统计请求的返回数据长度。
            //下面input_filter只是简单的拷贝buffer上面的数据总共n长度的,到u->out_bufs里面去,以待发送。
            if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {//一般为ngx_http_upstream_non_buffered_filter
                ngx_http_upstream_finalize_request(r, u, 0);
                return;
            }
			//开始发送数据到downstream,然后读取数据,然后发送,如此循环,知道不可读/不可写
            ngx_http_upstream_process_non_buffered_downstream(r);
        } else {//没有数据,长度为0,不需要发送了吧。不,要flush
            u->buffer.pos = u->buffer.start;
            u->buffer.last = u->buffer.start;
            if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) {
                ngx_http_upstream_finalize_request(r, u, 0);
                return;
            }
            if (u->peer.connection->read->ready) {//如果后端FCGI可读,则继续读取upstream的数据.然后发送
                ngx_http_upstream_process_non_buffered_upstream(r, u);
            }
        }
        return;
    }//!u->buffering结束

当后端的upstream连接可读时,会调用ngx_http_upstream_process_non_buffered_upstream函数进行处理,当天首先是调用c->read->handler 上的读写回调的,这个回调对于upstream来说固定为 ngx_http_upstream_handler。
ngx_http_upstream_process_non_buffered_upstream函数和ngx_http_upstream_process_non_buffered_downstream他们都只是做来一下超时判断,然后调用ngx_http_upstream_process_non_buffered_request。2者其实就一个区别:upstream 第二个参数为0,表示不用立即发送数据,因为没有数据可以发送,得先读取才行。所以我们直接看数据读写的集中地方,ngx_http_upstream_process_non_buffered_request。
这个函数做了几件事情:

0.调用过滤模块,将数据发送出去,do_write为是否要给客户端发送数据。
1.如果要发送,就调用ngx_http_output_filter将数据发送出去。
2.然后ngx_unix_recv读取数据,放入out_bufs里面去。如此循环。

ngx_http_upstream_process_non_buffered_request具体的过程在下面代码中说的比较明白了,另外下面代码中贴了ngx_http_output_filter函数的代码啊,这个事调用各个body过滤输出函数,将数据发送出去的函数。第一个是ngx_http_range_body_filter,最后一个body filter 是ngx_http_write_filter。

static void
ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r, ngx_uint_t do_write)
{//调用过滤模块,将数据发送出去,do_write为是否要给客户端发送数据。
//1.如果要发送,就调用ngx_http_output_filter将数据发送出去。
//2.然后ngx_unix_recv读取数据,放入out_bufs里面去。如此循环
    size_t                     size;
    ssize_t                    n;
    ngx_buf_t                 *b;
    ngx_int_t                  rc;
    ngx_connection_t          *downstream, *upstream;
    ngx_http_upstream_t       *u;
    ngx_http_core_loc_conf_t  *clcf;

    u = r->upstream;
    downstream = r->connection;//找到这个请求的客户端连接
    upstream = u->peer.connection;//找到上游的连接
    b = &u->buffer;//找到这坨要发送的数据,不过大部分都被input filter放到out_bufs里面去了。
    do_write = do_write || u->length == 0;//do_write为1时表示要立即发送给客户端。
    for ( ;; ) {
        if (do_write) {//要立即发送。
            if (u->out_bufs || u->busy_bufs) {
				//如果u->out_bufs不为NULL则说明有需要发送的数据,这是ngx_http_upstream_non_buffered_filter拷贝到这里的。
				//u->busy_bufs代表上次未发送完毕的数据.
                rc = ngx_http_output_filter(r, u->out_bufs);//一个个调用ngx_http_top_body_filter过滤模块,最终发送数据。
                if (rc == NGX_ERROR) {
                    ngx_http_upstream_finalize_request(r, u, 0);
                    return;
                }
				//下面将out_bufs的元素移动到busy_bufs的后面;将已经发送完毕的busy_bufs链表元素移动到free_bufs里面
                ngx_chain_update_chains(&u->free_bufs, &u->busy_bufs, &u->out_bufs, u->output.tag);
            }
            if (u->busy_bufs == NULL) {//busy_bufs没有了,都发完了。想要发送的数据都已经发送完毕
                if (u->length == 0 || upstream->read->eof || upstream->read->error) {
					//此时finalize request,结束这次请求
                    ngx_http_upstream_finalize_request(r, u, 0);
                    return;
                }
                b->pos = b->start;//重置u->buffer,以便与下次使用,从开始起
                b->last = b->start;
            }
        }//当前缓存里面的数据发送给客户端告一段落
        size = b->end - b->last;//得到当前buf的剩余空间,其实如果do_write=1,很可能就为全空的缓冲区
        if (size > u->length) {
            size = u->length;
        }
        if (size && upstream->read->ready) {//当前的这块buffer还有剩余空间,并且碰巧跟UPSTREAM的连接是可读的,也就是FCGI发送了数据。
/*为什么这里还有可读数据呢,不是已经接到FCGI的结束包了吗,因为之前只是读取完了HTTP头部,碰到\r\n\r\n后就退出了
   那问题来了,upstream怎么知道该怎么读取呢,FCGI,PROXY怎么办,看到这里我想gdb试试,于是找FCGI的buffering类似的选项,未果。查看网上信息:
   恍然大悟,FCGI协议是无所谓buffering了,它不是流式的数据,而是包式的一个个包,所以用的是buffering模式。
   http://www.ruby-forum.com/topic/197216
Yes. It's because of FastCGI protocol internals. It splits "stream"
into blocks max 32KB each. Each block has header info (how many bytes
it contains, etc). So nginx can't send content to the client until it
get the whole block from upstream.*/
            n = upstream->recv(upstream, b->last, size);//调用ngx_unix_recv读取数据到last成员,很简单的recv()就行了。
            if (n == NGX_AGAIN) {//下回再来,这回没数据了。
                break;
            }
            if (n > 0) {//读了一些数据,立马将它发送出去吧,也就是放入到out_bufs链表里面去,没有实际发送的,那么,什么时候发送呢
                u->state->response_length += n;//再次调用input_filter,这里没有reset u->buffer.last,这是因为我们这个值并没有更新.
                if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {//就是ngx_http_upstream_non_buffered_filter
                    ngx_http_upstream_finalize_request(r, u, 0);
                    return;
                }
            }
            do_write = 1;//因为刚刚无论如何n大于0,所以读取了数据,那么下一个循环会将out_bufs的数据发送出去的。
            continue;
        }
        break;
    }
//····
}

ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{//BODY发送函数,调用ngx_http_top_body_filter一个个将他们发送出去
//参数in为u->out_bufs,也就是待发送的数据,本函数完成后,u->out_bufs的数据将移动到busy_bufs
    ngx_int_t          rc;
    ngx_connection_t  *c;

    c = r->connection;
    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,  "http output filter \"%V?%V\"", &r->uri, &r->args);
    rc = ngx_http_top_body_filter(r, in);//一个个body filter调用,最终发送出数据。第一个是ngx_http_range_body_filter
    //最后一个body filter 是ngx_http_write_filter
    if (rc == NGX_ERROR) {
        /* NGX_ERROR may be returned by any filter */
        c->error = 1;
    }

    return rc;
}

无缓冲模式到这里介绍完毕了,这个比较简单:upstream有数据了就读取数据然后调用输出链发送出去;如果客户端连接可写了就把挂载的数据发送出去。反正就是有了一点发一点,不过FCGI不走无缓冲模式。
限于篇幅在下一篇文章中介绍缓冲模式的处理流程。Nginx upstream原理分析【2】-带buffering发送数据 。

Share
分类: C/C++, Nginx 标签: , ,

注意: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。使用'@all ',将会将评论发送给之前所有其它评论者。请务必注意user必须和评论者名相匹配(大小写一致)。