首页 > Nginx > Nginx upstream原理分析【3】-带buffering给客户端返回数据

Nginx upstream原理分析【3】-带buffering给客户端返回数据

2013年5月19日 发表评论 阅读评论 30781次阅读    

上一篇Nginx upstream原理分析【2】-带buffering读取upstream数据 我们介绍了nginx在带buffering的情况下是如何读取FCGI数据的,这里我们介绍他是怎么给客户端返回数据的。

从上篇文章我们知道ngx_event_pipe这个函数一句参数不同,既可以读取upstream数据,也可以给客户端返回数据。其调用的功能函数分别为:ngx_event_pipe_read_upstream 和ngx_event_pipe_write_to_downstream。nginx读取upstream的数据后,会把数据放在p->in链表里面,当然如果缓存不够等原因,写入了磁盘的话,nginx总是将p->in的前面部分写入磁盘,因此会记录在p->out链表上面。在发送时就围绕这2个成员进行了。
发送数据时,客户端的连接结构存放在p->downstream。先来看一下缓存结构:

struct ngx_buf_s {//内存的描述结构/管理结构,其start/end指向一块真正存放数据的内存。
    u_char          *pos;//当前数据读到了这里
    u_char          *last;//所有数据的末尾
    off_t            file_pos;//如果在文件中,那就表示为偏移
    off_t            file_last;///如果在文件中,那就表示为偏移

    u_char          *start;         /* start of buffer */
    u_char          *end;           /* end of buffer */
    ngx_buf_tag_t    tag;
    ngx_file_t      *file;
    ngx_buf_t       *shadow;//shadow会将buf组成一条链表。用last_shadow标记表明是否是某个大裸FCGI数据块中的最后一个。通过p->in等指向头部。
    //这里容易混淆,以为last_shadow指的是整个链表的最后一个,其实不是,这个链表中可能是属于几个大FCGI数据块,就有几个为1的。具体参考ngx_event_pipe_drain_chains

    /* the buf's content could be changed */
    unsigned         temporary:1;
    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    unsigned         memory:1;
    /* the buf's content is mmap()ed and must not be changed */
    unsigned         mmap:1;
    unsigned         recycled:1;//这个域表示我们当前的buf是需要被回收的。调用output_filter之前会设置为0.代表我这个buf是否需要回收重复利用。
    unsigned         in_file:1;//这个BUFFER存放在文件中。
    unsigned         flush:1;//这块内存是不是需要尽快flush给客户端,也就是是否需要尽快发送出去,ngx_http_write_filter会利用这个标志做判断。
    unsigned         sync:1;
    unsigned         last_buf:1;//是否是最后一块内存。
    unsigned         last_in_chain:1;

    unsigned         last_shadow:1;//这个代表的是,我这个data数据块是不是属于裸FCGI数据块的最后一个,如果是,那我的shadow指向这个大数据块。否则指向下一个data节点。
    unsigned         temp_file:1;
    /* STUB */ int   num;
};

上面有个标志:recycled,这个标志是用来控制内存回收的,如果标志位1,标志其他地方关注这块内存的回收,希望回收它。如果设置为0,表示不需要回收。在nginx读取完毕upstream的数据,FCGI返回FIN包后,我们由于不需要分配其他内存了,因此不需要回收这块内存,因此recycled会设置为0.

这里插入一下,在ngx_http_fastcgi_input_filter函数解析到一块有小数据后,会带哦用如下代码,设置recycled为1 !!!!为什么??那这样不就会导致nginx尽快的将数据发送给客户端吗?

		//用这个新的缓存描述结构,指向buf这块内存里面的标准输出数据部分,注意这里并没有拷贝数据,而是用b指向了f->pos也就是buf的某个数据地方。
        ngx_memzero(b, sizeof(ngx_buf_t));
        b->pos = f->pos;//从pos到end
        b->start = buf->start;//b 跟buf共享一块客户端发送过来的数据。这就是shadow的地方, 类似影子?
        b->end = buf->end;
        b->tag = p->tag;
        b->temporary = 1;
        b->recycled = 1;//设置为需要回收的标志,这样在发送数据时,会考虑回收这块内存的。但是,请看ngx_event_pipe_remove_shadow_links这里会设置为1的。

比如我gdb了一个nginx工作进程,其p->in->buf的内容是这样的:

$18 = {
pos = 0x1755f10 "h</td><td class=\"v\">/usr/sbin/sendmail&nbsp;-t&nbsp;-i&nbsp;</td><td class=\"v\">/usr/sbin/sendmail&nbsp;-t&n
bsp;-i&nbsp;</td></tr>\n<tr><td class=\"e\">serialize_precision</td><td class=\"v\">100</td><td cl"..., last = 0x1756f10 "@@",
file_pos = 0, file_last = 0,
start = 0x1755f10 "h</td><td class=\"v\">/usr/sbin/sendmail&nbsp;-t&nbsp;-i&nbsp;</td><td class=\"v\">/usr/sbin/sendmail&nbsp;-t
&nbsp;-i&nbsp;</td></tr>\n<tr><td class=\"e\">serialize_precision</td><td class=\"v\">100</td><td cl"..., end = 0x1756f10 "@@",
tag = 0x693940, file = 0x0, shadow = 0x17529f8, temporary = 1, memory = 0, mmap = 0, recycled = 1, in_file = 0, flush = 0,
sync = 0, last_buf = 0, last_in_chain = 0, last_shadow = 1, temp_file = 0, num = 3}

从上面可以看到,一般情况下recycled是设置为1了的,也就是说希望尽快回收这块内存,一般fastcgi_buffers设置的缓存也就几块,默认为:fastcgi_buffers 8 4k|8k ; 所以回收是肯定的。另外,可以看到 last_shadow为1,那就是说明这个内存覆盖了整块从upstream读取过来的数据。这样其shadow指针肯定指向这块数据的头部了。


下面回到ngx_event_pipe_write_to_downstream,不然跑题了。这个函数是个大的循环,不断的遍历p->out,p->in的数据。在循环开头,如果FCGI发送完了所有数据,发送了FIN包,那么会进入特殊的处理:直接清楚recycled标志,调用output_filter发送数据,这个函数就是ngx_http_output_filter函数,这部分比较简单,看一下代码:

static ngx_int_t
ngx_event_pipe_write_to_downstream(ngx_event_pipe_t *p)
{//ngx_event_pipe调用这里进行数据发送给客户端,数据已经准备在p-&gt;out,p-&gt;in里面了。
    u_char            *prev;
    size_t             bsize;
    ngx_int_t          rc;
    ngx_uint_t         flush, flushed, prev_last_shadow;
    ngx_chain_t       *out, **ll, *cl, file;
    ngx_connection_t  *downstream;

    downstream = p-&gt;downstream;
    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p-&gt;log, 0,&quot;pipe write downstream: %d&quot;, downstream-&gt;write-&gt;ready);
    flushed = 0;

    for ( ;; ) {
        if (p-&gt;downstream_error) {//如果客户端连接出错了。drain=排水;流干,
        //清空upstream发过来的,解析过格式后的HTML数据。将其放入free_raw_bufs里面。
            return ngx_event_pipe_drain_chains(p);
        }
        if (p-&gt;upstream_eof || p-&gt;upstream_error || p-&gt;upstream_done) {
//如果upstream的连接已经关闭了,或出问题了,或者发送完毕了,那就可以发送了。
            /* pass the p-&gt;out and p-&gt;in chains to the output filter */
            for (cl = p-&gt;busy; cl; cl = cl-&gt;next) {
                cl-&gt;buf-&gt;recycled = 0;
            }

            if (p-&gt;out) {//数据写到磁盘了
                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p-&gt;log, 0, &quot;pipe write downstream flush out&quot;);
                for (cl = p-&gt;out; cl; cl = cl-&gt;next) {
                    cl-&gt;buf-&gt;recycled = 0;//不需要回收重复利用了,因为upstream_done了,不会再给我发送数据了。
                }
				//下面,因为p-&gt;out的链表里面一块块都是解析后的HTML数据,所以直接调用ngx_http_output_filter进行HTML数据发送就行了。
                rc = p-&gt;output_filter(p-&gt;output_ctx, p-&gt;out);
                if (rc == NGX_ERROR) {
                    p-&gt;downstream_error = 1;
                    return ngx_event_pipe_drain_chains(p);
                }
                p-&gt;out = NULL;
            }

            if (p-&gt;in) {//跟out同理。简单调用ngx_http_output_filter进入各个filter发送过程中。
                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p-&gt;log, 0, &quot;pipe write downstream flush in&quot;);
                for (cl = p-&gt;in; cl; cl = cl-&gt;next) {
                    cl-&gt;buf-&gt;recycled = 0;//已经是最后的了,不需要回收了
                }
				//注意下面的发送不是真的writev了,得看具体情况比如是否需要recycled,是否是最后一块等。ngx_http_write_filter会判断这个的。
                rc = p-&gt;output_filter(p-&gt;output_ctx, p-&gt;in);//调用ngx_http_output_filter发送,最后一个是ngx_http_write_filter
                if (rc == NGX_ERROR) {
                    p-&gt;downstream_error = 1;
                    return ngx_event_pipe_drain_chains(p);
                }
                p-&gt;in = NULL;
            }
			//如果要缓存,那就写入到文件里面去。
            if (p-&gt;cacheable &amp;&amp; p-&gt;buf_to_file) {
                file.buf = p-&gt;buf_to_file;
                file.next = NULL;
                if (ngx_write_chain_to_temp_file(p-&gt;temp_file, &amp;file) == NGX_ERROR){
                    return NGX_ABORT;
                }
            }
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p-&gt;log, 0, &quot;pipe write downstream done&quot;);
            /* TODO: free unused bufs */
            p-&gt;downstream_done = 1;
            break;
        }

上面这部分代码如果进入,最终就会退出整个函数从而结束请求的,如果FCGI没有关闭,数据读取没有完成,那么就可以进行数据发送了。
Nginx有个配置参数:fastcgi_busy_buffers_size,我们看一下其定义:

syntax: fastcgi_busy_buffers_size size;
default: fastcgi_busy_buffers_size 8k|16k;
context: httpserverlocation

Limits the total size of buffers that can be busy sending a response to the client while the response is not yet fully read. In the mean time, the rest of the buffers can be used for reading a response and, if needed, buffering part of a response to a temporary file. By default, size is limited by two buffers set by the fastcgi_buffer_size and fastcgi_buffers directives.

简单说,busy代表经过了output_filter过的,从out移动过来的缓存,其里面可能有已经发送完成了,因为ngx_http_write_filter会更新这写buf的。因此nginx会在这个时候遍历计算一下busy buf的大小,看看是不是超过了大小,如果是,就会开始发送,也就是设置flush=1,直接跳过去到后面的goto语句goto flush;去调用output_filter,然后ngx_chain_update_chains更新busy,free指针,这个我们后面再介绍。

		//否则upstream数据还没有发送完毕。
        if (downstream-&gt;data != p-&gt;output_ctx || !downstream-&gt;write-&gt;ready || downstream-&gt;write-&gt;delayed) {
            break;
        }
        /* bsize is the size of the busy recycled bufs */
        prev = NULL;
        bsize = 0;
//这里遍历需要busy这个正在发送,已经调用过output_filter的buf链表,计算一下那些可以回收重复利用的buf
//计算这些buf的总容量,注意这里不是计算busy中还有多少数据没有真正writev出去,而是他们总共的最大容量
        for (cl = p-&gt;busy; cl; cl = cl-&gt;next) {
            if (cl-&gt;buf-&gt;recycled) {
                if (prev == cl-&gt;buf-&gt;start) {
                    continue;
                }
                bsize += cl-&gt;buf-&gt;end - cl-&gt;buf-&gt;start;
                prev = cl-&gt;buf-&gt;start;
            }
        }
        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p-&gt;log, 0, &quot;pipe write busy: %uz&quot;, bsize);
        out = NULL;
		//busy_size为fastcgi_busy_buffers_size 指令设置的大小,指最大待发送的busy状态的内存总大小。
		//如果大于这个大小,nginx会尝试去发送新的数据并回收这些busy状态的buf。
        if (bsize &gt;= (size_t) p-&gt;busy_size) {
            flush = 1;//如果busy链表里面的数据很多了,超过fastcgi_busy_buffers_size 指令,那就赶紧去发送,回收吧,不然free_raw_bufs里面没可用缓存了。
            goto flush;
        }

然后,就需要老老实实的看看p->out,p->in里面的数据了,后面的代码是个循环,不断遍历p->out,p->in里面的未发送数据,将他们放到out链表后面,注意这里发送的数据不超过busy_size因为配置限制了。这里有个比较有意思的地方就是,nginx会尽量一次发送一整块数据,具体方法看下面的代码中的注释,prev_last_shadow用来表示遍历过程之中,上一块内存的last_shadow标志值。综合起来就是一句话:必须要发送数据的时候,如果上一块数据正好是一块大裸FCGI内存的最后一个数据节点,那当前这个节点下次再进行发送,尽量一次一个FCGI数据块的发送
循环执行完后,我们就得到了一个HTML节点链表,由out指针指向,待会就可以发送这个链表的数据了。代码如下:

        flush = 0;
        ll = NULL;
        prev_last_shadow = 1;//标记上一个节点是不是正好是一块FCGI buffer的最后一个数据节点。
//遍历p-&gt;out,p-&gt;in里面的未发送数据,将他们放到out链表后面,注意这里发送的数据不超过busy_size因为配置限制了。
        for ( ;; ) {
//循环,这个循环的终止后,我们就能获得几块HTML数据节点,并且他们跨越了1个以上的FCGI数据块的并以最后一块带有last_shadow结束。
            if (p-&gt;out) {//buf到tempfile的数据会放到out里面。
                cl = p-&gt;out;
                if (cl-&gt;buf-&gt;recycled &amp;&amp; bsize + cl-&gt;buf-&gt;last - cl-&gt;buf-&gt;pos &gt; p-&gt;busy_size) {
                    flush = 1;//判断是否超过busy_size
                    break;
                }
                p-&gt;out = p-&gt;out-&gt;next;
                ngx_event_pipe_free_shadow_raw_buf(&amp;p-&gt;free_raw_bufs, cl-&gt;buf);
            } else if (!p-&gt;cacheable &amp;&amp; p-&gt;in) {
                cl = p-&gt;in;
                ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p-&gt;log, 0, &quot;pipe write buf ls:%d %p %z&quot;, cl-&gt;buf-&gt;last_shadow, cl-&gt;buf-&gt;pos, cl-&gt;buf-&gt;last - cl-&gt;buf-&gt;pos);
				//
                if (cl-&gt;buf-&gt;recycled &amp;&amp; cl-&gt;buf-&gt;last_shadow &amp;&amp; bsize + cl-&gt;buf-&gt;last - cl-&gt;buf-&gt;pos &gt; p-&gt;busy_size)  {
					//1.对于在in里面的数据,如果其需要回收;
					//2.并且又是某一块大FCGI buf的最后一个有效html数据节点;
					//3.而且当前的没法送的大小大于busy_size, 那就需要回收一下了,因为我们有buffer机制
                    if (!prev_last_shadow) {
		//如果前面的一块不是某个大FCGI buffer的最后一个数据块,那就将当前这块放入out的后面,然后退出循环去flash
		//什么意思呢,就是说,如果当前的这块不会导致out链表多加了一个节点,而倒数第二个节点正好是一块FCGI大内存的结尾。
		//其实是i做了个优化,让nginx尽量一块块的发送。
                        p-&gt;in = p-&gt;in-&gt;next;
                        cl-&gt;next = NULL;
                        if (out) {
                            *ll = cl;
                        } else {
                            out = cl;
                        }
                    }
                    flush = 1;//超过了大小,标记一下待会是需要真正发送的。不过这个好像没发挥多少作用,因为后面不怎么判断、
                    break;//停止处理后面的内存块,因为这里已经大于busy_size了。
                }
                prev_last_shadow = cl-&gt;buf-&gt;last_shadow;
                p-&gt;in = p-&gt;in-&gt;next;
            } else {
                break;//后面没有数据了,那没办法了,发吧。不过一般情况肯定有last_shadow为1的。这里很难进来的。
            }
//cl指向当前需要处理的数据,比如cl = p-&gt;out或者cl = p-&gt;in;
//下面就将这块内存放入out指向的链表的最后,ll指向最后一块的next指针地址。
            if (cl-&gt;buf-&gt;recycled) {//如果这块buf是需要回收利用的,就统计其大小
                bsize += cl-&gt;buf-&gt;last - cl-&gt;buf-&gt;pos;
            }
            cl-&gt;next = NULL;
            if (out) {
                *ll = cl;
            } else {
                out = cl;//指向第一块数据
            }
            ll = &amp;cl-&gt;next;
        }

找到要发送的数据后,下面进入flush的过程,out指针指向一个链表,其里面的数据是从p->out,p->in来的要发送的数据。后面将out指针指向的内存调用output_filter,进入filter过程。

0.首先调用output_filter()将这块数据进行filter,最后尝试发送出去。最终将r->out里面的数据,和参数里面的数据一并以writev的机制发送给客户端,如果没有发送完所有的,则将剩下的放在r->out。下次发送的时候从out开始继续发送。
1.如果输出失败,就需要释放相关的结构,调用的是ngx_event_pipe_drain_chains函数,这个我们待会介绍一下;
2.然后ngx_chain_update_chains()将各个链表整理一下,主要是将out刚刚发送完的buf挂入busy,busy里面已经确定调用了writev的内存放入free链表,以便后续进行重复利用。
3.之后,遍历free里面的数据,如果其中某个节点cl->buf->last_shadow等于1的话,说明我们碰到了一块FCGI裸数据的buf的最后一个数据节点,那么久调用ngx_event_pipe_add_free_buf将这个FCGI裸数据buf返回放到free_raw_bufs里面去。

看一下ngx_event_pipe_write_to_downstream最后一部分,也就是数据发送filter输出,整理链表的代码:

//到这里后,out指针指向一个链表,其里面的数据是从p-&gt;out,p-&gt;in来的要发送的数据。
    flush:
//下面将out指针指向的内存调用output_filter,进入filter过程。
        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p-&gt;log, 0, &quot;pipe write: out:%p, f:%d&quot;, out, flush);
        if (out == NULL) {
            if (!flush) {
                break;
            }
            /* a workaround for AIO */
            if (flushed++ &gt; 10) {
                return NGX_BUSY;
            }
        }
        rc = p-&gt;output_filter(p-&gt;output_ctx, out);//简单调用ngx_http_output_filter进入各个filter发送过程中。
        if (rc == NGX_ERROR) {
            p-&gt;downstream_error = 1;
            return ngx_event_pipe_drain_chains(p);
        }
		//将out的数据移动到busy,busy中发送完成的移动到free
        ngx_chain_update_chains(&amp;p-&gt;free, &amp;p-&gt;busy, &amp;out, p-&gt;tag);
        for (cl = p-&gt;free; cl; cl = cl-&gt;next) {
            if (cl-&gt;buf-&gt;temp_file) {
                if (p-&gt;cacheable || !p-&gt;cyclic_temp_file) {
                    continue;
                }
                /* reset p-&gt;temp_offset if all bufs had been sent */
                if (cl-&gt;buf-&gt;file_last == p-&gt;temp_file-&gt;offset) {
                    p-&gt;temp_file-&gt;offset = 0;
                }
            }
            /* TODO: free buf if p-&gt;free_bufs &amp;&amp; upstream done */
            /* add the free shadow raw buf to p-&gt;free_raw_bufs */
            if (cl-&gt;buf-&gt;last_shadow) {
	//前面说过了,如果这块内存正好是整个大FCGI裸内存的最后一个data节点,则释放这块大FCGI buffer。
	//当last_shadow为1的时候,buf-&gt;shadow实际上指向了这块大的FCGI裸buf的。也就是原始buf,其他buf都是个影子,他们指向某块原始的buf.
                if (ngx_event_pipe_add_free_buf(p, cl-&gt;buf-&gt;shadow) != NGX_OK) {
                    return NGX_ABORT;
                }
                cl-&gt;buf-&gt;last_shadow = 0;
            }
            cl-&gt;buf-&gt;shadow = NULL;
        }
    }
    return NGX_OK;
}

到这里,ngx_event_pipe_write_to_downstream处理完毕。下面我们附带介绍一下几个函数:
ngx_http_output_filter函数:
这个函数在很多地方会被调用,用来把第二个参数的链表的数据发送到r参数所指的客户端连接。一般是挂在某个结构的output_filter成员上,比如:rc = p->output_filter(p->output_ctx, out);
ngx_http_output_filter函数会调用一个叫ngx_http_top_body_filter的函数,这个函数会形成一个输出链,也就是函数链接,一个调用一个,负责将数据发送出去,这里有个有意思的地方,就是这个函数是个安全局函数指针,其到底指向什么地方呢,看编译的顺序,比如我安装的,这个函数指向ngx_http_range_body_filter,最后一个是:ngx_http_write_filter用来发送数据的,ngx_http_write_filter函数会调用writev真正的发送数据。下面看一下这个函数就知道这里的原理了,也就是通过static变量保存上一个ngx_http_top_body_filter,这样类似实现了一个函数调用栈:

&lt;/span&gt;
static ngx_int_t
ngx_http_range_body_filter_init(ngx_conf_t *cf)
{
ngx_http_next_body_filter = ngx_http_top_body_filter;//本文件的局部变量。
ngx_http_top_body_filter = ngx_http_range_body_filter;//覆盖全局变量

return NGX_OK;
}

ngx_event_pipe_drain_chains函数: 这个函数可以看出last_shadow机制的作用,具体看下面的注释吧:

static ngx_int_t
ngx_event_pipe_drain_chains(ngx_event_pipe_t *p)
{//遍历p-&gt;in/out/busy,将其链表所属的裸FCGI数据块释放,放入到free_raw_bufs中间去。也就是,清空upstream发过来的,解析过格式后的HTML数据。
    ngx_chain_t  *cl, *tl;

    for ( ;; ) {
 //····
		//找到对应的链表
        while (cl) {/*要知道,这里cl里面,比如p-&gt;in里面的这些ngx_buf_t结构所指向的数据内存实际上是在
        ngx_event_pipe_read_upstream里面的input_filter进行协议解析的时候设置为跟从客户端读取数据时的buf公用的,也就是所谓的影子。
		然后,虽然p-&gt;in指向的链表里面有很多很多个节点,每个节点代表一块HTML代码,但是他们并不是独占一块内存的,而是可能共享的,
		比如一块大的buffer,里面有3个FCGI的STDOUT数据包,都有data部分,那么将存在3个b的节点链接到p-&gt;in的末尾,他们的shadow成员
		分别指向下一个节点,最后一个节点就指向其所属的大内存结构。具体在ngx_http_fastcgi_input_filter实现。
        */
            if (cl-&gt;buf-&gt;last_shadow) {//碰到了某个大FCGI数据块的最后一个节点,释放只,然后进入下一个大块里面的某个小html 数据块。
                if (ngx_event_pipe_add_free_buf(p, cl-&gt;buf-&gt;shadow) != NGX_OK) {
                    return NGX_ABORT;
                }
                cl-&gt;buf-&gt;last_shadow = 0;
            }

            cl-&gt;buf-&gt;shadow = NULL;
            tl = cl-&gt;next;

            cl-&gt;next = p-&gt;free;//把cl这个小buf节点放入p-&gt;free,供ngx_http_fastcgi_input_filter进行重复使用。
            p-&gt;free = cl;

            cl = tl;
        }
    }
}

ngx_chain_update_chains函数: 这个函数用来更新busy,free链表。

void ngx_chain_update_chains(ngx_chain_t **free, ngx_chain_t **busy, ngx_chain_t **out, ngx_buf_tag_t tag)
{//调用方式 &amp;u-&gt;free_bufs, &amp;u-&gt;busy_bufs, &amp;u-&gt;out_bufs, u-&gt;output.tag
//函数完成2个功能: 1. 将out_bufs的缓冲区放入busy_bufs链表的尾部,注意顺序;
//2.如果busy_bufs里面的数据没有了,发送完毕了,那就将这块buffer缓冲区移动到free_bufs链表里面。
    ngx_chain_t  *cl;

    if (*busy == NULL) {//这2个循环,将&amp;u-&gt;out_bufs 移动到u-&gt;busy_bufs链表尾部。
        *busy = *out;
    } else {
        for (cl = *busy; cl-&gt;next; cl = cl-&gt;next) { /* void */ }
        cl-&gt;next = *out;
    }
    *out = NULL;
    while (*busy) {//将已经发送完毕的buf更新,放入free链表里面。
        if (ngx_buf_size((*busy)-&gt;buf) != 0) {//ngx_http_write_filter函数发送的时候会更新buf的。
            break;//由于这些buffer的顺序性,如果碰到大小不等于0的,也就是数据发送到这里之后的都没有发送出去,不能释放。
        }
        if ((*busy)-&gt;buf-&gt;tag != tag) {
            *busy = (*busy)-&gt;next;
            continue;
        }
        (*busy)-&gt;buf-&gt;pos = (*busy)-&gt;buf-&gt;start;//清空busy-&gt;buf结构里面的数据
        (*busy)-&gt;buf-&gt;last = (*busy)-&gt;buf-&gt;start;
	//下面四行,将busy指向的指针的指向往后移动,然后将当前节点放入free_bufs的头部
        cl = *busy;
        *busy = cl-&gt;next;
        cl-&gt;next = *free;
        *free = cl;
    }
}

ngx_event_pipe_add_free_buf函数: ngx_event_pipe_add_free_buf将参数的b代表的数据块挂入free_raw_bufs的开头或者第二个位置。

ngx_int_t
ngx_event_pipe_add_free_buf(ngx_event_pipe_t *p, ngx_buf_t *b)
{//将参数的b代表的数据块挂入free_raw_bufs的开头或者第二个位置。b为上层觉得没用了的数据块。
    ngx_chain_t  *cl;
//这里不会出现b就等于free_raw_bufs-&gt;buf的情况吗
    cl = ngx_alloc_chain_link(p-&gt;pool);
    if (cl == NULL) {
        return NGX_ERROR;
    }
    b-&gt;pos = b-&gt;start;//置空这坨数据
    b-&gt;last = b-&gt;start;
    b-&gt;shadow = NULL;

    cl-&gt;buf = b;

    if (p-&gt;free_raw_bufs == NULL) {
        p-&gt;free_raw_bufs = cl;
        cl-&gt;next = NULL;
        return NGX_OK;
    }
	//看下面的注释,意思是,如果最前面的free_raw_bufs中没有数据,那就吧当前这块数据放入头部就行。
	//否则如果当前free_raw_bufs有数据,那就得放到其后面了。为什么会有数据呢?比如,读取一些数据后,还剩下一个尾巴存放在free_raw_bufs,然后开始往客户端写数据
	//写完后,自然要把没用的buffer放入到这里面来。这个是在ngx_event_pipe_write_to_downstream里面做的。或者干脆在ngx_event_pipe_drain_chains里面做。
	//因为这个函数在inpupt_filter里面调用是从数据块开始处理,然后到后面的,
	//并且在调用input_filter之前是会将free_raw_bufs置空的。应该是其他地方也有调用。
    if (p-&gt;free_raw_bufs-&gt;buf-&gt;pos == p-&gt;free_raw_bufs-&gt;buf-&gt;last) {
        /* add the free buf to the list start */
        cl-&gt;next = p-&gt;free_raw_bufs;
        p-&gt;free_raw_bufs = cl;
        return NGX_OK;
    }
    /* the first free buf is partialy filled, thus add the free buf after it */
    cl-&gt;next = p-&gt;free_raw_bufs-&gt;next;
    p-&gt;free_raw_bufs-&gt;next = cl;
    return NGX_OK;
}

ngx_event_pipe_write_to_downstream函数到这里算介绍完了,相对ngx_event_pipe_read_upstream要简单一些。这里总结一下:
ngx_event_pipe_write_to_downstream负责将p->out, p->in链表里面的data数据节点的数据按照FCGI裸数据buffer为单位,也就是以块为单位调用ngx_http_output_filter,将数据链接到r->out成员上,随时准备用writev的方式发送出去。然后更新free,busy等结构,将已经发送出去的buffer挂到free_raw_bufs链表上以备后续循环利用。

前面2篇文章:Nginx upstream原理分析【1】-无缓冲模式发送数据
Nginx upstream原理分析【2】-带buffering读取upstream数据

Share
分类: Nginx 标签: , ,
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.

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