12.8.4 ngx_event_pipe_read_upstream方法

ngx_event_pipe_read_upstream方法负责接收上游的响应,在这个过程中会涉及以下4种情况。

❑接收响应头部时可能接收到部分包体。

❑如果没有达到bufs.num上限,那么可以分配bufs.size大小的内存块充当接收缓冲区。

❑如果恰好下游的连接处于可写状态,则应该优先发送响应来清理出空闲缓冲区。

❑如果缓冲区全部写满,则应该写入临时文件。

这4种情况会造成ngx_event_pipe_read_upstream方法较为复杂,特别是任何一个ngx_buf_t缓冲区都存在复用的情况,什么时候释放、重复使用它们会很麻烦,因此,首先把纯粹地接收自上游缓冲区的代码提取出来,概括为流程图,如图12-11所示,读者首先看看到底怎样接收上游响应,然后再来分析ngx_event_pipe_read_upstream方法。

12.8.4 ngx_event_pipe_read_upstream方法 - 图1

图 12-11 使用缓冲区接收上游响应的流程图

图12-11中的步骤很清晰,主要是在寻找使用哪一块缓冲区接收上游响应。注意,在选择缓冲区时也有优先级。下面我们分析其中的7个步骤。

1)首先检查ngx_event_pipe_t结构体中的preread_bufs缓冲区(若无特殊说明,以下介绍的成员都属于ngx_event_pipe_t结构体),它存放着在接收响应包头时可能接收到的包体(图12-9中的第4步初始化了preread_bufs缓冲区),如果preread_bufs中有内容,意味着需要优先处理这部分包体,而不是去接收更多的包体,这样,接收流程就结束了。如果preread_bufs缓冲区是空的,那么继续向下执行。

2)检查free_raw_bufs缓冲区链表,free_raw_bufs用来表示一次ngx_event_pipe_read_upstream方法调用过程中接收到的上游响应。注意,free_raw_bufs链表中缓冲区的顺序与接收顺序是相反的,每次使用缓冲区接收到上游发来的响应后,都会把该缓冲区添加到free_raw_bufs末尾。如果free_raw_bufs为空,则继续第3步执行;否则,跳到第6步使用free_raw_bufs缓冲区接收上游响应。

3)将已经分配的缓冲区数量(allocated成员)与bufs.num配置相比,如果allocated小于bufs.num,则可以从pool内存池中分配到一块新的缓冲区,再跳到第6步用这块缓冲区来接收上游响应,否则说明分配的缓冲区已经达到上限,跳到第4步继续向下执行。

4)检查Nginx与下游的连接downstream成员,检查它的写事件的ready标志位,如果ready为1,则表示当前可以向下游发送响应,再检查写事件的delayed标志位,如果delayed为0,则说明并不是由于限速才使得写事件准备好,这两个条件都满足时表明应当由向下游发送响应来释放缓冲区,以期可以使用释放出的空闲缓冲区再接收上游响应。怎么做到呢?将upstream_blocked置为1即可。当无法满足上述条件时,继续执行第5步。

5)检查临时文件中已经写入的响应内容长度(也就是temp_file->offset)是否达到配置上限(也就是max_temp_file_size配置),如果已经达到,则暂时不再接收上游响应;如果没有达到,调用ngx_event_pipe_write_chain_to_temp_file方法将响应写入临时文件中。下面简单地看看这个方法做了些什么:首先将in缓冲区链表中的内容写入temp_file临时文件中,再把写入临时文件的ngx_buf_t缓冲区由in缓冲区链表中移出,添加到out缓冲区链表中。在写入临时文件成功后,跳到第6步使用free_raw_bufs缓冲区接收上游响应。

6)调用recv_chain方法接收上游的响应。

7)将新接收到的缓冲区置到free_raw_bufs链表的最后。

图12-11中的这7个步骤将会找出一个缓冲区接收上游的响应,并把这个缓冲区添加到free_raw_bufs链表中,下面我们以此为基础,看看图12-12中ngx_event_pipe_read_upstream方法是如何处理free_raw_bufs链表的。

图12-12展示了ngx_event_pipe_read_upstream方法的全部流程,其中主要包括一个接收上游响应的循环,而每一次接收到上游响应后又会有一个循环来处理free_raw_bufs链表中的全部缓冲区,下面详细分析一下这11个步骤。

12.8.4 ngx_event_pipe_read_upstream方法 - 图2

图 12-12 ngx_event_pipe_read_upstream方法接收上游响应的流程

1)检查上游连接是否结束,以及与上游连接的读事件是否已经就绪,代码如下。


//这3个标志位的意义可参见12.8.1节,其中任一个为1都表示上游连接需要结束

if(p->upstream_eof||p->upstream_error||p->upstream_done){

//跳到第9步执行

break;

}

/如果读事件的ready标志位为0,则说明没有上游响应可以接收;preread_bufs预读缓冲区为空,表示接收包头时没有收到包体,或者收到过包体但已经处理过了/

if(p->preread_bufs==NULL&&!p->upstream->read->ready){

//跳到第9步执行

break;

}


如果这两个条件有一个满足,则需要跳到第9步,准备结束ngx_event_pipe_read_upstream方法,否则继续执行第2步。

2)接收上游响应,这一步实际上就是执行图12-11中列出的7个步骤。它会导致3种结果:①如果free_raw_bufs链表中有需要处理的包体,则跳到第3步执行;②执行到图12-11的第4步分支,upstream_blocked标志位置为1,同时,ngx_event_pipe_read_upstream方法返回NGX_OK;③如果没有接收到包体,则跳到第8步执行。

3)置read标志位为1,表示接收到的包体待处理。

4)从接收到的缓冲区链表中取出一块ngx_buf_t缓冲区。

5)调用ngx_event_pipe_remove_shadow_links方法将这块缓冲区中的shadow域释放掉,因为刚刚接收到的缓冲区,必然不存在多次引用的情况,所以shadow成员要指向空指针。

6)检查本次读取到的包体是否大于或等于缓冲区的剩余空间大小。这一步的意义在于,如果当前接收到的长度小于缓冲区长度,则说明这个缓冲区还可以用于再次接收响应,这时跳到第7步执行;否则,这个缓冲区已满,则应该调用input_filter方法处理,当然,默认的input_filter方法就是ngx_event_pipe_copy_input_filter,它所做的事情就是在in链表中添加这个缓冲区。继续循环执行第4步,遍历本次接收到的所有缓冲区。

7)将本次接收到的缓冲区添加到free_raw_bufs链表末尾,继续第1步执行这个大循环。

8)将upstream_eof标志位置为1,表示上游服务器已经关闭了连接。

9)检查upstream_eof和upstream_error标志位是否有任意一个为1,如果有,则说明上游连接已经结束,这时如果free_raw_bufs缓冲区链表不为空,则需要跳到第10步处理free_raw_bufs中的缓冲区;否则返回NGX_OK,结束ngx_event_pipe_read_upstream方法。

10)再次调用input_filter方法处理free_raw_bufs中的缓冲区(类似第6步,但这次只处理可能剩余的最后一个缓冲区)。

11)检查free_bufs标志位,如果free_bufs为1,则说明需要尽快释放缓冲区中用到的内存,这时调用ngx_pfree方法释放shadow域为空的缓冲区。

可以看到,ngx_event_pipe_read_upstream方法将会把接收到的响应存放到内存或者磁盘文件中,同时用ngx_buf_t缓冲区指向这些响应,最后用in和out缓冲区链表把这些ngx_buf_t缓冲区管理起来。图12-12只是展示了ngx_event_pipe_read_upstream方法的主要流程,如果需要理解这种转发时缓冲区的详细用法,还需要对照着图12-11和图12-12来阅读ngx_event_pipe.c源文件。