nginx的filter的处理

来源:互联网 发布:淘宝网帽子围巾手套 编辑:程序博客网 时间:2024/06/14 20:01
@using CSDN.BLOG.Model@using CSDN.BLOG.Helper@using CSDN.Common

推荐专家

    @{ var list = PeopleHelper.GetRecommend(false); if (list != null) {Html.RenderPartial("_peopleview2", list); } }
nginx的filter的处理

文章分类:C++编程
随笔拿一个nginx的filter模块来看,gzip模块,来看它的初始化。

Java代码 复制代码 收藏代码
  1. static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;   
  2. static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;   
  3.   
  4. static ngx_int_t   
  5. ngx_http_gzip_filter_init(ngx_conf_t *cf)   
  6. {   
  7.     ngx_http_next_header_filter = ngx_http_top_header_filter;   
  8.     ngx_http_top_header_filter = ngx_http_gzip_header_filter;   
  9.   
  10.     ngx_http_next_body_filter = ngx_http_top_body_filter;   
  11.     ngx_http_top_body_filter = ngx_http_gzip_body_filter;   
  12.   
  13.     return NGX_OK;   
  14. }  



这里nginx处理filter将所有的过滤器做成一个类似链表的东东,每次声明一个ngx_http_next_header_filter以及ngx_http_next_body_filter来保存当前的最前面的filter,然后再将自己的filter处理函数赋值给ngx_http_top_header_filter以及ngx_http_top_body_filter ,这样也就是说最后面初始化的filter反而是最早处理。

而在模块本身的filter处理函数中会调用ngx_http_next_header_filter,也就是当前filter插入前的那个最top上的filter处理函数。

然后我们来看nginx如何启动filter的调用。

先来看head_filter的调用:

Java代码 复制代码 收藏代码
  1. ngx_int_t   
  2. ngx_http_send_header(ngx_http_request_t *r)   
  3. {   
  4.     if (r->err_status) {   
  5.         r->headers_out.status = r->err_status;   
  6.         r->headers_out.status_line.len = 0;   
  7.     }   
  8.   
  9.     return ngx_http_top_header_filter(r);   
  10. }  


可以看到当发送header的时候就是调用ngx_http_top_header_filter,nginx这里把status这些也作为一个filter模块来处理的。当启动ngx_http_top_header_filter之后所有的filter处理函数就会象链表一样被一个个的调用。


然后是body filter的调用,这个和header的类似,因此就不解释了。
Java代码 复制代码 收藏代码
  1. ngx_int_t   
  2. ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)   
  3. {   
  4.     ngx_int_t          rc;   
  5.     ngx_connection_t  *c;   
  6.   
  7.     c = r->connection;   
  8.   
  9.     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,   
  10.                    "http output filter \"%V?%V\"", &r->uri, &r->args);   
  11.   
  12. //启动body filter。   
  13.     rc = ngx_http_top_body_filter(r, in);   
  14. ..............................................................   
  15.   
  16.     return rc;   
  17. }  


这里还有一个问题,那就是最后一个ngx_http_top_header_filter和ngx_http_top_body_filter是什么呢?也就是第一个被插入的filter。

先来看filter被初始化的地方。这里filter的初始化是在ngx_http_block函数中:

Java代码 复制代码 收藏代码
  1. for (m = 0; ngx_modules[m]; m++) {   
  2.         if (ngx_modules[m]->type != NGX_HTTP_MODULE) {   
  3.             continue;   
  4.         }   
  5.   
  6.         module = ngx_modules[m]->ctx;   
  7.   
  8. //如果存在postconfiguratio则调用初始化。  
  9.         if (module->postconfiguration) {   
  10.             if (module->postconfiguration(cf) != NGX_OK) {   
  11.                 return NGX_CONF_ERROR;   
  12.             }   
  13.         }   
  14.     }  


代码很简单就是遍历ngx_modules然后调用初始化函数,而我们这里要找第一个filter,也就是ngx_modules中的第一个bodyfilter和header filter。

来看objs/ngx_modules.c中的ngx_module的定义:

Java代码 复制代码 收藏代码
  1. ngx_module_t *ngx_modules[] = {   
  2.  ..............................................................   
  3.     &ngx_http_write_filter_module,   
  4.     &ngx_http_header_filter_module,   
  5. ........................................................................   
  6.     NULL   
  7. };  


可以看到ngx_http_write_filter_module和ngx_http_header_filter_module分别是body filter和header filter的第一个初始化模块,也就是filter链中的最后一个模块。

接下来我们就来详细分析这两个模块,首先是ngx_http_write_filter_module模块。

这个模块的功能起始很简单,就是遍历chain,然后输出所有的数据,如果有设置flush的话刷新chain。

这里要注意ngx_http_request_t中有一个out的chain,这个chain保存的是上一次还没有被发完的buf,这样每次我们接收到新的chain的话,就需要将新的chain连接到老的out chain上,然后再发出去。

来看代码。

Java代码 复制代码 收藏代码
  1. ngx_int_t   
  2. ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)  


第一个是request请求,第二个参数是输入的chain。

先来看初始化部分:


Java代码 复制代码 收藏代码
  1.      
  2.  off_t                      size, sent, nsent, limit;   
  3.     ngx_uint_t                 last, flush;   
  4.     ngx_msec_t                 delay;   
  5.     ngx_chain_t               *cl, *ln, **ll, *chain;   
  6.     ngx_connection_t          *c;   
  7.     ngx_http_core_loc_conf_t  *clcf;   
  8.   
  9. //得到当前所属的连接   
  10.     c = r->connection;   
  11.   
  12.     if (c->error) {   
  13.         return NGX_ERROR;   
  14.     }   
  15.   
  16.     size = 0;   
  17.     flush = 0;   
  18.     last = 0;   
  19. //得到上次没有发送完毕的chain   
  20.     ll = &r->out;  


然后接下来这部分是校验并统计out chain,也就是上次没有完成的chain buf。
 
Java代码 复制代码 收藏代码
  1.     
  2.  for (cl = r->out; cl; cl = cl->next) {   
  3.         ll = &cl->next;   
  4.   
  5. #if 1  
  6. //如果有0长度的buf则返回错误。   
  7.         if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {   
  8.  ......................................................................   
  9.   
  10.             ngx_debug_point();   
  11.             return NGX_ERROR;   
  12.         }   
  13. #endif   
  14.   
  15. //得到buf的大小   
  16.         size += ngx_buf_size(cl->buf);   
  17. //看当传输完毕后是否要刷新buf。   
  18.         if (cl->buf->flush || cl->buf->recycled) {   
  19.             flush = 1;   
  20.         }   
  21. //看是否是最后一个buf   
  22.         if (cl->buf->last_buf) {   
  23.             last = 1;   
  24.         }   
  25.     }  


接下来这部分是用来链接新的chain到上面的out chain后面:

Java代码 复制代码 收藏代码
  1. for (ln = in; ln; ln = ln->next) {   
  2. //   
  3.         cl = ngx_alloc_chain_link(r->pool);   
  4.         if (cl == NULL) {   
  5.             return NGX_ERROR;   
  6.         }   
  7.   
  8.         cl->buf = ln->buf;   
  9. //前面的代码我们知道ll已经指向out chain的最后一个位置了,因此这里就是将新的chain链接到out chain的后面。  
  10.         *ll = cl;   
  11.         ll = &cl->next;   
  12. #if 1  
  13. //校验buf   
  14.         if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {   
  15.   
  16.             ngx_debug_point();   
  17.             return NGX_ERROR;   
  18.         }   
  19. #endif   
  20.   
  21. //计算大小   
  22.         size += ngx_buf_size(cl->buf);   
  23.   
  24. //判断是否需要flush   
  25.         if (cl->buf->flush || cl->buf->recycled) {   
  26.             flush = 1;   
  27.         }   
  28.   
  29. //判断是否是最后一个buf   
  30.         if (cl->buf->last_buf) {   
  31.             last = 1;   
  32.         }   
  33.     }  


然后接下来的这段代码主要是对进行发送前buf的一些标记的处理。

在看代码之前先来解释下几个比较重要的标记。

第一个是ngx_http_core_module的conf的一个标记postpone_output(conf里面可以配置的),这个表示延迟输出的阀,也就是说将要发送的字节数如果小于这个的话,并且还有另外几个条件的话(下面会解释),就会直接返回不发送当前的chain。

第二个是c->write->delayed,这个表示当前的连接的写必须要被delay了,也就是说现在不能发送了(原因下面会解释),得等另外的地方取消了delayed才能发送,此时我们修改连接的buffered的标记,然后返回NGX_AGAIN.

第三个是c->buffered,因为有时buf并没有发完,因此我们有时就会设置buffed标记,而我们可能会在多个filter模块中被buffered,因此下面就是buffered的类型。

Java代码 复制代码 收藏代码
  1. //这个并没有用到   
  2. #define NGX_HTTP_LOWLEVEL_BUFFERED         0xf0  
  3. //主要是这个,这个表示在最终的write filter中被buffered  
  4. #define NGX_HTTP_WRITE_BUFFERED            0x10  
  5. //判断是否有被设置   
  6. #define NGX_LOWLEVEL_BUFFERED  0x0f  
  7.   
  8. //下面几个filter中被buffered   
  9. #define NGX_HTTP_GZIP_BUFFERED             0x20  
  10. #define NGX_HTTP_SSI_BUFFERED              0x01  
  11. #define NGX_HTTP_SUB_BUFFERED              0x02  
  12. #define NGX_HTTP_COPY_BUFFERED             0x04  


然后我们来看第二个的意思,这个表示当前的chain已经被buffered了,


第四个是r->limit_rate,这个表示当前的request的发送限制速率,这个也是在nginx.conf中配置的,而一般就是通过这个值来设置c->write->delayed的。也就是说如果发送速率大于这个limit了的话,就设置delayed,然后这边的request就会延迟发送,下面我们的代码会看到nginx如何处理。

  
Java代码 复制代码 收藏代码
  1.   
  2.  clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);   
  3.   
  4. //也就是说将要发送的字节数小于postpone_output并且不是最后一个buf,并且不需要刷新chain的话,就直接返回。  
  5.     if (!last && !flush && in && size < (off_t) clcf->postpone_output) {   
  6.         return NGX_OK;   
  7.     }   
  8.   
  9. ///如果设置了write的delayed,则设置标记。  
  10.     if (c->write->delayed) {   
  11.         c->buffered |= NGX_HTTP_WRITE_BUFFERED;   
  12.         return NGX_AGAIN;   
  13.     }   
  14.   
  15. //如果size为0,并且没有设置buffered标记,则进入清理工作。  
  16.     if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED)) {   
  17. //如果是最后一个buf,则清理buffered标记然后清理out chain  
  18.         if (last) {   
  19.             r->out = NULL;   
  20.             c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;   
  21.   
  22.             return NGX_OK;   
  23.         }   
  24.   
  25. //如果有设置flush的话,则会强行传输当前buf之前的所有buf,因此这里就需要清理out chain。  
  26.         if (flush) {   
  27.             do {   
  28.                 r->out = r->out->next;   
  29.             } while (r->out);   
  30.   
  31. //清理buf 标记   
  32.             c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;   
  33.   
  34.             return NGX_OK;   
  35.         }   
  36.   
  37.         ngx_log_error(NGX_LOG_ALERT, c->log, 0,   
  38.                       "the http output chain is empty");   
  39.   
  40.         ngx_debug_point();   
  41.   
  42.         return NGX_ERROR;   
  43.     }   
  44.   
  45. //如果有发送速率限制。   
  46.     if (r->limit_rate) {   
  47. //计算是否有超过速率限制   
  48.         limit = r->limit_rate * (ngx_time() - r->start_sec + 1)   
  49.                 - (c->sent - clcf->limit_rate_after);   
  50. //如果有   
  51.         if (limit <= 0) {   
  52. //设置delayed标记   
  53.             c->write->delayed = 1;   
  54. //设置定时器   
  55.             ngx_add_timer(c->write,   
  56.                           (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1));   
  57.   
  58. //设置buffered。   
  59.             c->buffered |= NGX_HTTP_WRITE_BUFFERED;   
  60.   
  61.             return NGX_AGAIN;   
  62.         }   
  63.   
  64.     } else if (clcf->sendfile_max_chunk) {   
  65. //sendfile所用到的limit。   
  66.         limit = clcf->sendfile_max_chunk;   
  67.   
  68.     } else {   
  69.         limit = 0;   
  70.     }   
  71.   
  72.     sent = c->sent;  


然后接下来这段就是发送buf,以及发送完的处理部分。这里要注意send_chain返回值为还没有发送完的chain,这个函数我后面的blog会详细的分析的。

Java代码 复制代码 收藏代码
  1.   
  2. //调用发送函数。   
  3. chain = c->send_chain(c, r->out, limit);   
  4.   
  5.     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,   
  6.                    "http write filter %p", chain);   
  7.   
  8.     if (chain == NGX_CHAIN_ERROR) {   
  9.         c->error = 1;   
  10.         return NGX_ERROR;   
  11.     }   
  12.   
  13. //控制imit_rate,这个值一般是在nginx.conf中配置的。  
  14.     if (r->limit_rate) {   
  15.   
  16.         nsent = c->sent;   
  17.   
  18.         if (clcf->limit_rate_after) {   
  19.   
  20.             sent -= clcf->limit_rate_after;   
  21.             if (sent < 0) {   
  22.                 sent = 0;   
  23.             }   
  24.   
  25.             nsent -= clcf->limit_rate_after;   
  26.             if (nsent < 0) {   
  27.                 nsent = 0;   
  28.             }   
  29.         }   
  30.   
  31.         delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate + 1);   
  32.   
  33.         if (delay > 0) {   
  34.             c->write->delayed = 1;   
  35.             ngx_add_timer(c->write, delay);   
  36.         }   
  37.   
  38.     } else if (c->write->ready   
  39.                && clcf->sendfile_max_chunk   
  40.                && (size_t) (c->sent - sent)   
  41.                       >= clcf->sendfile_max_chunk - 2 * ngx_pagesize)   
  42.     {   
  43.         c->write->delayed = 1;   
  44.         ngx_add_timer(c->write, 1);   
  45.     }   
  46.   
  47. //开始遍历上一次还没有传输完毕的chain,如果这次没有传完的里面还有的话,就跳出循环,否则free这个chain  
  48.     for (cl = r->out; cl && cl != chain; /* void */) {   
  49.         ln = cl;   
  50.         cl = cl->next;   
  51.         ngx_free_chain(r->pool, ln);   
  52.     }   
  53.   
  54. ///out chain赋值   
  55.     r->out = chain;   
  56.   
  57. //如果chain存在,则设置buffered并且返回again。  
  58.     if (chain) {   
  59.         c->buffered |= NGX_HTTP_WRITE_BUFFERED;   
  60.         return NGX_AGAIN;   
  61.     }   
  62.   
  63. //否则清理buffered   
  64.     c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;   
  65.   
  66. //如果有其他的filter buffered并且postponed被设置了,则我们返回again,也就是还有buf要处理。  
  67.     if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {   
  68.         return NGX_AGAIN;   
  69.     }   
  70.   
  71. //否则返回ok   
  72.     return NGX_OK;  



然后我们来看ngx_http_header_filter_module模块,这个模块的处理函数是ngx_http_header_filter。这个函数最终还是会调用ngx_http_write_filter来将head输出。

这个函数主要就是处理http的头域,然后设置对应的reponse值,最终输出。

这里header filter比较简单,这里没有什么复杂的东西,主要就是设置一些status。然后拷贝,最后通过ngx_http_write_filter进行发送
 
 
 

1.为什么需要内存池

    为什么需要内存池?

a. 在大量的小块内存的申请和释放的时候,能更快地进行内存分配(对比malloc和free)

b.减少内存碎片,防止内存泄露。

2.内存池的原理

    内存池的原理非常简单,用申请一块较大的内存来代替N多的小内存块,当有需要malloc一块

比较小的内存是,直接拿这块大的内存中的地址来用即可。

    当然,这样处理的缺点也是很明显的,申请一块大的内存必然会导致内存空间的浪费,但是

比起频繁地malloc和free,这样做的代价是非常小的,这是典型的以空间换时间。

    一个典型的内存池如下图所示:

MemoryPool_Step5

 

 

 

 

 

 

 

                 图一:一个典型的内存池。

 

    首先定义这样一个结构体:

 typedef struct MemoryBlock{  char *Data ;//数据  std::size_t DataSize ;//总的大小  std::size_t UsedSize ;//已经用了的大小  MemoryBlock*Next ;} MemoryBlock;

    一个内存池就是这样一连串的内存块组成。当需要用到内存的时候,调用此内存池定义好的接口

GetMemory(),而需要删除的时候FreeMemory()。

    而GetMemory和FreeMemory干了什么呢?GetMemory只是简单返回内存池中可用空间的地址。

而FreeMemory干了两件事情:一: 改变UsedSize 的值,二:重新初始化这一内存区域。

 

3.nginx中的内存池

3.1 nginx内存池的结构表示

    首先我们看一下nginx内存池的定义:

 

代码
struct ngx_pool_s {    ngx_pool_data_t       d;//表示数据区域    size_t                       max;//内存池能容纳数据的大小    ngx_pool_t *             current;//当前内存池块(nginx中的内存池是又一连串的内存池链表组成的)    ngx_chain_t*             chain;//主要为了将内存池连接起来    ngx_pool_large_t*      large;//大块的数据    ngx_pool_cleanup_t*  cleanup;//清理函数    ngx_log_t*                 log;//写log};

 

nginx中的内存池和普通的有比较大的不同。nginx中的内存池是由N个内存池链表

组成的,当一个内存池满了以后,就会从下一个内存池中提取空间来使用。 

  

对于ngx_pool_data_t的定义非常简单

 

typedef struct {    u_char               *last;    u_char               *end;    ngx_pool_t         *next;    ngx_uint_t          failed;} ngx_pool_data_t;

 

其中last表示当前数据区域的已经使用的数据的结尾。

end表示当前内存池的结尾。

next表示下一个内存池,前面已经说过,再nignx中,当一个内存池空间

不足的时候,它不会扩大其空间,而是再新建一个内存池,组成一个内存池链表。

failed标志申请内存的时候失败的次数。

 

在理解了这个结构体后面的就非常简单了。

 

current 表示当前的内存池。

 

chain表示内存池链表。

 

large表示大块的数据。

对于ngx_pool_large_t定义如下:

 

struct ngx_pool_large_s {    ngx_pool_large_t*        next;    void*                alloc;};

 

此结构体的定义也是非常简单的。一个内存地址的指针已经指向下一个地址的指针。

这里再解释下为什么需要有large数据块。当一个申请的内存空间大小比内存池的大小还要大的时候,

malloc一块大的空间,再内存池用保留这个地址的指针。


Cleanup保持存着内存池被销毁的时候的清理函数。

typedef void (*ngx_pool_cleanup_pt)(void *data);
struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; void* data; ngx_pool_cleanup_t* next;};

 

ngx_pool_cleanup_pt 是一个函数指针的典型用法,

在这个结果中保存这需要清理的数据指针以及相应的清理函数, 让内存池销毁

或其他需要清理内存池的时候,可以调用此结构体中的handler。

    下面是我画的一张nginx的内存池的结构图。

                        ngx_pool
                                                        <图1. ngx_pool 结构体>

3.2 nginx内存池源代码分析

    要大体了解一个内存池,只需要了解其池子的创建,内存的分配以及池子的销毁即可。下面就分析下

ngx_pool_t的这个3个方面。注:其中有些代码可能与ngx_pool中的源代码有所差异,但是整体意思

绝对是一样的,本人的修改,只是为了更好的分析,比如 我就把所有写log的过程都去掉了。

3.2.1 ngx_create_pool

创建一个内存池

 ngx_pool_t* ngx_create_poo(size_t size){ngx_pool_t*p;p = (ngx_pool_t*)malloc(size);if (!p){return NULL;}//计算内存池的数据区域p->d->last = (u_char*)p + sizeof(ngx_pool_t);p->d->end = (u_char*)p + size;p->d->next = NULL;//下个内存池p->d->failed = 0;size = size - sizeof(ngx_pool_t);;p->max = size;//最大数据//我现在还是是一个单一的内存池p->current = p;p->chain = NULL;//只有在需要的时候才分配大的内存区域p->large = NULL;p->cleanup = NULL;return p;}


    nginx内存池的创建非常简单,申请一开size大小的内存,把它分配给 ngx_poo_t。

3.2.2 ngx_palloc

从内存池中分配内存.

 void* ngx_palloc(ngx_pool_t* pool, size_t size){u_char*m;ngx_pool_t*p;//遍历内存池,拿出可用的内存区域if (size <= pool->max){p = pool->current;do {m = p->d->last;if ((size_t)(p->d->end - m) >= size) {p->d->last = m + size;//用掉了当然要改变*last了return m;}p = p->d->next;} while (p);return ngx_palloc_block(pool, size);//所有的内存池都已经满了,我要再增加一个}//申请的内存超过了内存池的大小,所以用return ngx_palloc_large(pool, size);}

 

 

    这个函数从内存池用拿出内存,如果当前内存池已满,到下一个内存池,如果所有的内存池已满,

增加一个新的内存池,如果申请的内存超过了内存池的最大值,从*large中分配

3.3.3  ngx_destroy_pool

内存池的销毁

 void ngx_destroy_pool(ngx_pool_t* pool){ngx_pool_t          *p, *n;ngx_pool_large_t    *l;ngx_pool_cleanup_t  *c;//调用清理函数for (c = pool->cleanup; c; c = c->next) {if (c->handler) {c->handler(c->data);}}//释放大块的内存for (l = pool->large; l; l = l->next) {if (l->alloc) {free(l->alloc);}}//小块的内存,真正意义上的内存池for (p = pool, n = pool->d->next; /* void */; p = n, n = n->d->next) {free(p);//如果当前内存池为空,之后的毕为空if (n == NULL) {break;}}}

 

 

    销毁一个内存池其实就是干了三件事, 调用清理韩式, 释放大块的内存,释放内存池,需要注意的

一点是在nginx中, 小块内存除了在内存池被销毁的时候都是不能被释放的。

 

3.3.4 ngx_palloc_block

    前面说过,在nginx中,当内存池满了以后,会增加一个新的内存池。这个动作就是靠ngx_palloc_block

函数实现的。

 static void* ngx_palloc_block(ngx_pool_t* pool, size_t size){u_char      *m;size_t       psize;ngx_pool_t  *p, *pnew, *current;psize = (size_t) (pool->d->end - (u_char *) pool);m = (u_char*)malloc(psize);if (!m){returnNULL;}//一个新的内存池pnew = (ngx_pool_t*) m;pnew->d->end = m +psize;pnew->d->next = NULL;pnew->d->failed = 0;//是不是和ngx_palloc很相似啊m += sizeof(ngx_pool_data_t);pnew->d->last = m + size;current = pool->current;//遍历到内存池链表的末尾for (p = current; p->d->next; p = p->d->next) {if (p->d->failed++ > 4) {//为什么4?推测是个经验值current = p->d->next;}}p->d->next = pnew;pool->current = current ? current : pnew;return m;}

 

    这个函数就是申请了一块内存区域,变为一个内存池,然后把它连接到原来内存池的末尾。

3.3.5 ngx_palloc_large 和ngx_pfree

    在nginx中,小块内存除了在内存池销毁之外是不能释放的,但是大块内存却可以,这两个

函数就是用来控制大块内存的申请和释放, 代码也非常简单,调用malloc申请内存,连接到

ngx_pool_large_t中 和 调用free释放内存。这里就不贴上代码了。

 

4. 小结

    不知不觉,写了快一个下午的时间了,真快啊。

     nginx的内存池的代码也先介绍到这里,其实nginx内存池功能强大,所以代码也比较复杂,

这里只

 

 

 

1顶
1踩
nginx中锁的设计以及惊群的处理 |nginx的filter的处理
2010-04-24

nginx中的output chain的处理(一)

文章分类:C++编程
这里我们详细来看ngx_linux_sendfile_chain方法,这个函数也就是nginx的发送函数。

一般来说,我们最终都会调用这个函数来发送最终的数据,因此我们来着重分析这个函数,这里主要就是对buf的一些参数的理解。

来看函数原型:
Java代码 复制代码 收藏代码
  1. ngx_chain_t *   
  2. ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)  

第一个参数是当前的连接,第二个参数是所需要发送的chain,第三个参数是所能发送的最大值。

然后来看这里的几个重要的变量:

Java代码 复制代码 收藏代码
  1.   
  2. send 表示将要发送的buf已经已经发送的大小。   
  3. sent表示已经发送的buf的大小   
  4. prev_send 表示上一次发送的大小,也就是已经发送的buf的大小。   
  5. fprev 和prev-send类似,只不过是file类型的。   
  6.   
  7. complete表示是否buf被完全发送了,也就是sent是否等于send - prev_send.   
  8.   
  9. header表示需要是用writev来发送的buf。也就是only in memory的buf。   
  10.   
  11. struct iovec  *iov, headers[NGX_HEADERS] 这个主要是用于sendfile和writev的参数,这里注意上面header数组保存的就是iovec。  


然后我们来看初始化

   
Java代码 复制代码 收藏代码
  1. wev = c->write;   
  2.   
  3.     if (!wev->ready) {   
  4.         return in;   
  5.     }   
  6.   
  7.     if (limit == 0 || limit > (off_t) (NGX_SENDFILE_LIMIT - ngx_pagesize)) {   
  8.         limit = NGX_SENDFILE_LIMIT - ngx_pagesize;   
  9.     }   
  10.   
  11.   
  12.     send = 0;   
  13. //设置header,也就是in memory的数组   
  14.     header.elts = headers;   
  15.     header.size = sizeof(struct iovec);   
  16.     header.nalloc = NGX_HEADERS;   
  17.     header.pool = c->pool;  


这里nginx的处理核心思想就是合并内存连续并相邻的buf(不管是in memory还是in file)

下面这段代码就是处理in memory的部分,然后将buf放入对应的iovec数组。

Java代码 复制代码 收藏代码
  1.   
  2. //开始遍历   
  3. for (cl = in;   
  4.              cl && header.nelts < IOV_MAX && send < limit;   
  5.              cl = cl->next)   
  6.         {   
  7.             if (ngx_buf_special(cl->buf)) {   
  8.                 continue;   
  9.             }   
  10. //如果不止是在buf中,这是因为有时in file的文件我们可能需要内存中也有拷贝,所以如果一个buf同时in memoey和in file的话,nginx会认为它是in file的来处理。  
  11.             if (!ngx_buf_in_memory_only(cl->buf)) {   
  12.                 break;   
  13.             }   
  14.   
  15. //得到buf的大小   
  16.             size = cl->buf->last - cl->buf->pos;   
  17.   
  18. //大于limit的话修改为size   
  19.             if (send + size > limit) {   
  20.                 size = limit - send;   
  21.             }   
  22. //如果prev等于pos,则说明当前的buf的数据和前一个buf的数据是连续的。  
  23.             if (prev == cl->buf->pos) {   
  24.                 iov->iov_len += (size_t) size;   
  25.   
  26.             } else {   
  27. //否则说明是不同的buf,因此add一个iovc。   
  28.                 iov = ngx_array_push(&header);   
  29.                 if (iov == NULL) {   
  30.                     return NGX_CHAIN_ERROR;   
  31.                 }   
  32.   
  33.                 iov->iov_base = (void *) cl->buf->pos;   
  34.                 iov->iov_len = (size_t) size;   
  35.             }   
  36.   
  37. //这里可以看到prev保存了当前buf的结尾。   
  38.             prev = cl->buf->pos + (size_t) size;   
  39. //更新发送的大小   
  40.             send += size;   
  41.         }  


然后是in file的处理这里比较核心的一个判断就是fprev == cl->buf->file_pos,和上面的in memory类似,fprev保存的就是上一次处理的buf的尾部。这里如果这两个相等,那就说明当前的两个buf是连续的(文件连续).

ok.来看代码。

Java代码 复制代码 收藏代码
  1.   
  2. //可以看到如果header的大小不为0则说明前面有需要发送的buf,因此我们就跳过in file处理  
  3. if (header.nelts == 0 && cl && cl->buf->in_file && send < limit) {   
  4. //得到file   
  5.             file = cl->buf;   
  6.   
  7. //开始合并。   
  8.             do {   
  9. //得到大小   
  10.                 size = cl->buf->file_last - cl->buf->file_pos;   
  11.   
  12. //如果太大则进行对齐处理。   
  13.                 if (send + size > limit) {   
  14.                     size = limit - send;   
  15.   
  16.                     aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)   
  17.                                & ~((off_t) ngx_pagesize - 1);   
  18.   
  19.                     if (aligned <= cl->buf->file_last) {   
  20.                         size = aligned - cl->buf->file_pos;   
  21.                     }   
  22.                 }   
  23.   
  24. //设置file_size.   
  25.                 file_size += (size_t) size;   
  26. //设置需要发送的大小   
  27.                 send += size;   
  28. //和上面的in memory处理一样就是保存这次的last  
  29.                 fprev = cl->buf->file_pos + size;   
  30.                 cl = cl->next;   
  31.   
  32.             } while (cl   
  33.                      && cl->buf->in_file   
  34.                      && send < limit   
  35.                      && file->file->fd == cl->buf->file->fd   
  36.                      && fprev == cl->buf->file_pos);   
  37.         }  


然后就是发送部分,这里in file使用sendfile,in memory使用writev.这里处理比较简单,就是发送然后判断发送的大小

Java代码 复制代码 收藏代码
  1. if (file) {   
  2. #if 1  
  3.             if (file_size == 0) {   
  4.                 ngx_debug_point();   
  5.                 return NGX_CHAIN_ERROR;   
  6.             }   
  7. #endif   
  8. #if (NGX_HAVE_SENDFILE64)   
  9.             offset = file->file_pos;   
  10. #else  
  11.             offset = (int32_t) file->file_pos;   
  12. #endif   
  13.   
  14. //发送数据   
  15.             rc = sendfile(c->fd, file->file->fd, &offset, file_size);   
  16. ......................................................   
  17. //得到发送的字节数   
  18.             sent = rc > 0 ? rc : 0;   
  19.   
  20.         } else {   
  21.             rc = writev(c->fd, header.elts, header.nelts);   
  22. .......................................................................   
  23.   
  24.             sent = rc > 0 ? rc : 0;   
  25. }   
  26.           


接下来这部分就是更新标记的部分,主要是buf的标记。

这里要注意一个地方,那就是ngx_buf_size部分,这个宏很简单就是判断buf是不是在memory中,如果是的话,就用pos和last计算,否则认为是在file中。

可是这里就有个问题了,如果一个buf本来是在file中的,我们由于某种原因,在内存中也有一份拷贝,可是我们并没有修改内存中的副本,于是如果我们还需要切割这个buf,这个时候,如果last和pos也就是buf对应的指针没有设置正确的话,这里就会出现问题了。

这里我觉得应该还有个标记,那就是如果内存中的副本我只是只读的话,发送的时候不应该算它在memory中。

Java代码 复制代码 收藏代码
  1.   
  2. //如果send - prev_send == sent则说明该发送的都发完了。  
  3. if (send - prev_send == sent) {   
  4.             complete = 1;   
  5.         }   
  6. //更新congnect的sent域。   
  7.         c->sent += sent;   
  8.   
  9. //开始重新遍历chain,这里是为了防止没有发送完全的情况,此时我们就需要切割buf了。  
  10.         for (cl = in; cl; cl = cl->next) {   
  11.   
  12.             if (ngx_buf_special(cl->buf)) {   
  13.                 continue;   
  14.             }   
  15.   
  16.             if (sent == 0) {   
  17.                 break;   
  18.             }   
  19. //得到buf size   
  20.             size = ngx_buf_size(cl->buf);   
  21.   
  22. //如果大于当前的size,则说明这个buf的数据已经被完全发送完毕了。,因此更新它的域。  
  23.             if (sent >= size){   
  24. //更新sent域   
  25.                 sent -= size;   
  26. //如果在内存则更新pos   
  27.                 if (ngx_buf_in_memory(cl->buf)) {   
  28.                     cl->buf->pos = cl->buf->last;   
  29.                 }   
  30. //如果在file   
  31.                 if (cl->buf->in_file) {   
  32.                     cl->buf->file_pos = cl->buf->file_last;   
  33.                 }   
  34.   
  35.                 continue;   
  36.             }   
  37.   
  38. //到这里说明当前的buf只有一部分被发送出去了,因此这里我们只需要修改指针。以便于下次发送。  
  39.             if (ngx_buf_in_memory(cl->buf)) {   
  40.                 cl->buf->pos += (size_t) sent;   
  41.             }   
  42. //同上。   
  43.             if (cl->buf->in_file) {   
  44.                 cl->buf->file_pos += sent;   
  45.             }   
  46.   
  47.             break;   
  48.         }  


最后一部分就是一些是否退出循环的操作。这里要注意,nginx中如果发送未完全的话,将会直接返回的,返回的就是没有发送完毕的chain,它的buf也已经被更新。这是因为nginx是单线程的,不能有任何意义的空跑和阻塞,因此当complete为0,nginx就认为是系统负载过大,此时直接返回,然后处理其他的事情,等待和下次的chain一起发送。

Java代码  
  1.   
  2. if (eintr) {   
  3.             continue;   
  4.         }   
  5. //如果未完成,则返回。   
  6.         if (!complete) {   
  7.             wev->ready = 0;   
  8.             return cl;   
  9.         }   
  10.   
  11.         if (send >= limit || cl == NULL) {   
  12.             return cl;   
  13.         }   
  14. //更新in,也就是开始处理下一个chain   
  15.         in = cl;
  16. 是列出了内存池的大体流程,还有很到一部分代码未列出来
 
 
着上次的分析继续,这次我们来看filter链中最关键的一个模块,那就是ngx_http_copy_filter_module模块,这个filter主要是用来将一些需要复制的buf(文件或者内存)重新复制一份然后发送给剩余的body filter,这里有个很重要的部分,那就是在这里nginx的剩余的body filter有可能会被调用多次,这个接下来我会一一阐述的。先来看它的初始化函数:

Java代码 复制代码 收藏代码
  1. static ngx_int_t   
  2. ngx_http_copy_filter_init(ngx_conf_t *cf)   
  3. {   
  4.     ngx_http_next_filter = ngx_http_top_body_filter;   
  5.     ngx_http_top_body_filter = ngx_http_copy_filter;   
  6.   
  7.     return NGX_OK;   
  8. }  


可以看到,它只有body filter,而没有header filter,也就是说只有body filter才会使用这个filter。

然后这个模块对应也有一个命令,那就是output_buffers,这个命令保存值在它的conf的bufs中:

Java代码 复制代码 收藏代码
  1. typedef struct {   
  2.     ngx_bufs_t  bufs;   
  3. } ngx_http_copy_filter_conf_t;  


这里要知道在nginx的配置文件中所有的bufs的格式都是一样,个数+每个的大小。这个值我们接下来分析filter代码的时候会再次看到。

然后来看对应的merge方法,来看这个bufs的默认值是多少。

Java代码 复制代码 收藏代码
  1. static char *   
  2. ngx_http_copy_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)   
  3. {   
  4.     ngx_http_copy_filter_conf_t *prev = parent;   
  5.     ngx_http_copy_filter_conf_t *conf = child;   
  6.   
  7. //默认是1个buf,大小为32768字节   
  8.     ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, 132768);   
  9.   
  10.     return NULL;   
  11. }  


由于copy filter没有header filter,因此它的context的初始化也是放在body filter中的,而它的ctx就是ngx_output_chain_ctx_t,为什么名字是output_chain呢,这是因为copy filter的主要逻辑的处理都是放在ngx_output_chain中的,这个模块我们可以看到它是保存在core目录下的,而不是属于http目录的。

接下来我们就来看这个context的结构。

Java代码 复制代码 收藏代码
  1. typedef struct {   
  2. //保存临时的buf   
  3.     ngx_buf_t                   *buf;   
  4. //保存了将要发送的chain   
  5.     ngx_chain_t                 *in;   
  6. //保存了已经发送完毕的chain,以便于重复利用   
  7.     ngx_chain_t                 *free;   
  8. //保存了还未发送的chain   
  9.     ngx_chain_t                 *busy;   
  10.   
  11. //sendfile标记   
  12.     unsigned                     sendfile:1;   
  13. //directio标记   
  14.     unsigned                     directio:1;   
  15. #if (NGX_HAVE_ALIGNED_DIRECTIO)   
  16.     unsigned                     unaligned:1;   
  17. #endif   
  18. //是否需要在内存中保存一份(使用sendfile的话,内存中没有文件的拷贝的,而我们有时需要处理文件,此时就需要设置这个标记)  
  19.     unsigned                     need_in_memory:1;   
  20. //是否存在的buf复制一份,这里不管是存在在内存还是文件,后面会看到这两个标记的区别。  
  21.     unsigned                     need_in_temp:1;   
  22.   
  23.     ngx_pool_t                  *pool;   
  24. //已经allocated的大小   
  25.     ngx_int_t                    allocated;   
  26. //对应的bufs的大小,这个值就是我们loc conf中设置的bufs  
  27.     ngx_bufs_t                   bufs;   
  28. //表示现在处于那个模块(因为upstream也会调用output_chain)  
  29.     ngx_buf_tag_t                tag;   
  30.   
  31. //这个值一般是ngx_http_next_filter,也就是继续调用filter链  
  32.     ngx_output_chain_filter_pt   output_filter;   
  33. //当前filter的上下文,这里也是由于upstream也会调用output_chain  
  34.     void                        *filter_ctx;   
  35. } ngx_output_chain_ctx_t;  



接下来我们来看具体函数的实现,就能更好的理解context中的这些域的意思。

来看copy_filter的body filter。

Java代码 复制代码 收藏代码
  1. static ngx_int_t   
  2. ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in)   
  3. {   
  4.     ngx_int_t                     rc;   
  5.     ngx_connection_t             *c;   
  6.     ngx_output_chain_ctx_t       *ctx;   
  7.     ngx_http_copy_filter_conf_t  *conf;   
  8.   
  9.     c = r->connection;   
  10.   
  11. //获取ctx   
  12.     ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module);   
  13.   
  14. //如果为空,则说明需要初始化ctx   
  15.     if (ctx == NULL) {   
  16.         conf = ngx_http_get_module_loc_conf(r, ngx_http_copy_filter_module);   
  17.   
  18.         ctx = ngx_pcalloc(r->pool, sizeof(ngx_output_chain_ctx_t));   
  19.         if (ctx == NULL) {   
  20.             return NGX_ERROR;   
  21.         }   
  22.   
  23.         ngx_http_set_ctx(r, ctx, ngx_http_copy_filter_module);   
  24.   
  25. //设置对应的域   
  26.         ctx->sendfile = c->sendfile;   
  27. //可以看到如果我们给request设置filter_need_in_memory的话,ctx的这个域就会被设置  
  28.         ctx->need_in_memory = r->main_filter_need_in_memory   
  29.                               || r->filter_need_in_memory;   
  30. //和上面类似   
  31.         ctx->need_in_temp = r->filter_need_temporary;   
  32.   
  33.         ctx->pool = r->pool;   
  34.         ctx->bufs = conf->bufs;   
  35.         ctx->tag = (ngx_buf_tag_t) &ngx_http_copy_filter_module;   
  36. //可以看到output_filter就是body filter的next  
  37.         ctx->output_filter = (ngx_output_chain_filter_pt) ngx_http_next_filter;   
  38. //此时filter ctx为当前的请求   
  39.         ctx->filter_ctx = r;   
  40.   
  41.         r->request_output = 1;   
  42.     }   
  43. //最关键的函数,下面会详细分析。   
  44.     rc = ngx_output_chain(ctx, in);   
  45. ......................................................   
  46.   
  47.     return rc;   
  48. }  


然后就是ngx_output_chain这个函数了,这里nginx filter的主要逻辑都在这个函数里面。下面就是这个函数的原型。

Java代码 复制代码 收藏代码
  1. ngx_int_t   
  2. ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in)  


然后我们来分段看它的代码,下面这段代码可以说是一个short path,也就是说我们能直接确定所有的in chain都不需要复制的时候,我们就可以直接调用output_filter来交给剩下的filter去处理。

  
Java代码 复制代码 收藏代码
  1.  if (ctx->in == NULL && ctx->busy == NULL) {   
  2.   
  3. //下面的注释解释的很详细   
  4.         /*  
  5.          * the short path for the case when the ctx->in and ctx->busy chains 
  6.          * are empty, the incoming chain is empty too or has the single buf 
  7.          * that does not require the copy 
  8.          */  
  9.   
  10.         if (in == NULL) {   
  11.             return ctx->output_filter(ctx->filter_ctx, in);   
  12.         }   
  13.   
  14. //这里说明只有一个chain,并且它的buf不需要复制  
  15.         if (in->next == NULL   
  16. #if (NGX_SENDFILE_LIMIT)   
  17.             && !(in->buf->in_file && in->buf->file_last > NGX_SENDFILE_LIMIT)   
  18. #endif   
  19.             && ngx_output_chain_as_is(ctx, in->buf))   
  20.         {   
  21.             return ctx->output_filter(ctx->filter_ctx, in);   
  22.         }   
  23.     }  


上面我们看到了一个函数 ngx_output_chain_as_is,这个函数很关键,下面还会再次被调用,这个函数主要用来判断是否需要复制buf。返回1,表示不需要拷贝,否则为需要拷贝
Java代码 复制代码 收藏代码
  1. static ngx_inline ngx_int_t   
  2. ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf)   
  3. {   
  4.     ngx_uint_t  sendfile;   
  5.   
  6. //是否为specialbuf,是的话返回1,也就是不用拷贝  
  7.     if (ngx_buf_special(buf)) {   
  8.         return 1;   
  9.     }   
  10.   
  11. //如果buf在文件中,并且使用了directio的话,需要拷贝buf  
  12.     if (buf->in_file && buf->file->directio) {   
  13.         return 0;   
  14.     }   
  15.   
  16. //sendfile标记   
  17.     sendfile = ctx->sendfile;   
  18.   
  19. #if (NGX_SENDFILE_LIMIT)   
  20. //如果pos大于sendfile的限制,设置标记为0  
  21.     if (buf->in_file && buf->file_pos >= NGX_SENDFILE_LIMIT) {   
  22.         sendfile = 0;   
  23.     }   
  24.   
  25. #endif   
  26.   
  27.     if (!sendfile) {   
  28. //此时如果buf不在内存中,则我们就需要复制到内存一份。  
  29.         if (!ngx_buf_in_memory(buf)) {   
  30.             return 0;   
  31.         }   
  32. //否则设置in_file为0.   
  33.         buf->in_file = 0;   
  34.     }   
  35.   
  36. //如果需要内存中有一份拷贝,而并不在内存中,此时返回0,表示需要拷贝  
  37.     if (ctx->need_in_memory && !ngx_buf_in_memory(buf)) {   
  38.         return 0;   
  39.     }   
  40.   
  41. //如果需要内存中有拷贝,并且存在于内存中或者mmap中,则返回0.  
  42.     if (ctx->need_in_temp && (buf->memory || buf->mmap)) {   
  43.         return 0;   
  44.     }   
  45.   
  46.     return 1;   
  47. }  


上面有两个标记要注意,一个是need_in_memory ,这个主要是用于当我们使用sendfile的时候,nginx并不会将请求文件拷贝到内存中,而有时我们需要操作文件的内容,此时我们就需要设置这个标记(设置方法前面初始化有介绍).然后我们在body filter就能操作内容了。

第二个是need_in_temp,这个主要是用于把本来就存在于内存中的buf复制一份拷贝出来,这里有用到的模块有charset,也就是编解码 filter.

然后接下来这段是复制in chain到ctx->in的结尾.它是通过调用ngx_output_chain_add_copy来进行add copy的,这个函数比较简单,这里就不分析了,不过只有一个要注意的,那就是如果buf是存在于文件中,并且file_pos超过了sendfile limit,此时就会切割buf为两个buf,然后保存在两个chain中,最终连接起来.

Java代码 复制代码 收藏代码
  1. if (in) {   
  2. //复制到ctx->in中.   
  3.         if (ngx_output_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) {   
  4.             return NGX_ERROR;   
  5.         }   
  6.     }  


然后就是主要的逻辑处理阶段。这里nginx做的非常巧妙也非常复杂,首先是chain的重用,然后是buf的重用。

先来看chain的重用。关键的几个结构以及域,ctx的free,busy以及ctx->pool的chain域。
其中每次发送没有发完的chain就放到busy中,而已经发送完毕的就放到free中,而最后会调用  ngx_free_chain来将free的chain放入到pool->chain中,而在ngx_alloc_chain_link中,如果pool->chain中存在chain的话,就不用malloc了,而是直接返回pool->chain,我们来看相关的代码。

Java代码 复制代码 收藏代码
  1. //链接cl到pool->chain中   
  2. #define ngx_free_chain(pool, cl)                                             \   
  3.     cl->next = pool->chain;                                                  \   
  4.     pool->chain = cl   
  5.   
  6. ngx_chain_t *   
  7. ngx_alloc_chain_link(ngx_pool_t *pool)   
  8. {   
  9.     ngx_chain_t  *cl;   
  10.   
  11.     cl = pool->chain;   
  12. //如果cl存在,则直接返回cl   
  13.     if (cl) {   
  14.         pool->chain = cl->next;   
  15.         return cl;   
  16.     }   
  17. //否则才会malloc chain   
  18.     cl = ngx_palloc(pool, sizeof(ngx_chain_t));   
  19.     if (cl == NULL) {   
  20.         return NULL;   
  21.     }   
  22.   
  23.     return cl;   
  24. }  


然后是buf的重用,严格意义上来说buf的重用是从free中的chain中取得的,当free中的buf被重用,则这个buf对应的chain就会被链接到ctx->pool中,从而这个chain就会被重用.

也就是说buf的重用是第一被考虑的,只有当这个chain的buf确定不需要被重用(或者说已经被重用)的时候,chain才会被链接到ctx->pool中被重用。

还有一个就是ctx的allocated域,这个域表示了当前的上下文中已经分配了多少个buf,blog一开始我们有提到有个output_buffer命令用来设置output的buf大小以及buf的个数。而allocated如果比output_buffer大的话,我们就需要先发送完已经存在的buf,然后才能再次重新分配buf。

来看代码,上面所说的重用以及buf的控制,代码里面都可以看的比较清晰。这里代码我们分段来看,下面这段主要是拷贝buf前所做的一些工作,比如判断是否拷贝,以及给buf分贝内存等。

Java代码 复制代码 收藏代码
  1. //out为我们最终需要传输的chain,也就是交给剩下的filter处理的chain  
  2.  out = NULL;   
  3. //last_out为out的最后一个chain   
  4.     last_out = &out;   
  5.     last = NGX_NONE;   
  6.   
  7. for ( ;; ) {   
  8.   
  9. //开始遍历chain   
  10.         while (ctx->in) {   
  11.   
  12. //取得当前chain的buf大小   
  13.             bsize = ngx_buf_size(ctx->in->buf);   
  14.   
  15. //跳过bsize为0的buf   
  16.             if (bsize == 0 && !ngx_buf_special(ctx->in->buf)) {   
  17.                 ngx_debug_point();   
  18.   
  19.                 ctx->in = ctx->in->next;   
  20.   
  21.                 continue;   
  22.             }   
  23.   
  24. //判断是否需要复制buf   
  25.             if (ngx_output_chain_as_is(ctx, ctx->in->buf)) {   
  26.   
  27.                 /* move the chain link to the output chain */  
  28. //如果不需要复制,则直接链接chain到out,然后继续循环  
  29.                 cl = ctx->in;   
  30.                 ctx->in = cl->next;   
  31.   
  32.                 *last_out = cl;   
  33.                 last_out = &cl->next;   
  34.                 cl->next = NULL;   
  35.   
  36.                 continue;   
  37.             }   
  38.   
  39. //到达这里,说明我们需要拷贝buf,这里buf最终都会被拷贝进ctx->buf中,因此这里先判断ctx->buf是否为空  
  40.             if (ctx->buf == NULL) {   
  41.   
  42. //如果为空,则取得buf,这里要注意,一般来说如果没有开启directio的话,这个函数都会返回NGX_DECLINED的(具体实现可以去看这个函数的代码)。  
  43.                 rc = ngx_output_chain_align_file_buf(ctx, bsize);   
  44.   
  45.                 if (rc == NGX_ERROR) {   
  46.                     return NGX_ERROR;   
  47.                 }   
  48.   
  49. //大部分情况下,都会落入这个分支   
  50.                 if (rc != NGX_OK) {   
  51.   
  52. //准备分配buf,首先在free中寻找可以重用的buf  
  53.                     if (ctx->free) {   
  54.   
  55.                         /* get the free buf */  
  56. //得到free buf   
  57.                         cl = ctx->free;   
  58.                         ctx->buf = cl->buf;   
  59.                         ctx->free = cl->next;   
  60. //将要重用的chain链接到ctx->poll中,以便于chain的重用.  
  61.                         ngx_free_chain(ctx->pool, cl);   
  62.   
  63.                     } else if (out || ctx->allocated == ctx->bufs.num) {   
  64. //如果已经等于buf的个数限制,则跳出循环,发送已经存在的buf.这里可以看到如果out存在的话,nginx会跳出循环,然后发送out,等发送完会再次处理,这里很好的体现了nginx的流式处理  
  65.                         break;   
  66.   
  67.                     } else if (ngx_output_chain_get_buf(ctx, bsize) != NGX_OK) {   
  68. //这个函数也比较关键,它用来取得buf.我们接下来会详细看这个函数  
  69.                         return NGX_ERROR;   
  70.                     }   
  71.                 }   
  72.             }   
  73. ............................................................   
  74.     }  


上面的代码分析的时候有个很关键的函数,那就是ngx_output_chain_get_buf,这个函数是当没有可重用的buf的时候,用来分配buf的。

这里只有一个要注意的,那就是如果当前的buf是位于最后一个chain的话,会有特殊处理。这里特殊处理有两个地方,一个是buf的recycled域,一个是将要分配的buf的大小。

先来说recycled域,这个域表示我们当前的buf是需要被回收的。而我们知道nginx一般情况下(比如非last buf)是会缓存一部分buf,然后再发送的(默认是1460字节),而设置了recycled的话,我们就不会让它缓存buf,也就是尽量发送出去,然后以供我们回收使用。

因此如果是最后一个buf的话,一般来说我们是不需要设置recycled域的,否则的话,需要设置recycled域。因为不是最后一个buf的话,我们可能还会需要重用一些buf,而buf只有被发送出去的话,我们才能重用。

然后就是size的大小。这里会有两个大小,一个是我们需要复制的buf的大小,一个是nginx.conf中设置的size。如果不是最后一个buf,则我们只需要分配我们设置的buf的size大小就行了。如果是最后一个buf,则就处理不太一样,下面的代码会看到。

Java代码 复制代码 收藏代码
  1. static ngx_int_t   
  2. ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx, off_t bsize)   
  3. {   
  4.     size_t       size;   
  5.     ngx_buf_t   *b, *in;   
  6.     ngx_uint_t   recycled;   
  7.   
  8.     in = ctx->in->buf;   
  9. //可以看到这里分配的buf,每个的大小都是我们在nginx.conf中设置的size  
  10.     size = ctx->bufs.size;   
  11. //默认有设置recycled域.   
  12.     recycled = 1;   
  13. //如果当前的buf是属于最后一个chain的时候。这里我们要特殊处理。  
  14.     if (in->last_in_chain) {   
  15. //这边注释很详细,我就不解释了.   
  16.         if (bsize < (off_t) size) {   
  17.   
  18.             /*  
  19.              * allocate a small temp buf for a small last buf 
  20.              * or its small last part 
  21.              */  
  22.             size = (size_t) bsize;   
  23.             recycled = 0;   
  24.   
  25.         } else if (!ctx->directio   
  26.                    && ctx->bufs.num == 1  
  27.                    && (bsize < (off_t) (size + size / 4)))   
  28.         {   
  29.             /*  
  30.              * allocate a temp buf that equals to a last buf, 
  31.              * if there is no directio, the last buf size is lesser 
  32.              * than 1.25 of bufs.size and the temp buf is single 
  33.              */  
  34.   
  35.             size = (size_t) bsize;   
  36.             recycled = 0;   
  37.         }   
  38.     }   
  39. //开始分配buf内存.   
  40.     b = ngx_calloc_buf(ctx->pool);   
  41.     if (b == NULL) {   
  42.         return NGX_ERROR;   
  43.     }   
  44.   
  45.     if (ctx->directio) {   
  46. //directio需要对齐   
  47.   
  48.         b->start = ngx_pmemalign(ctx->pool, size, NGX_DIRECTIO_BLOCK);   
  49.         if (b->start == NULL) {   
  50.             return NGX_ERROR;   
  51.         }   
  52.   
  53.     } else {   
  54. //大部分情况会走到这里.   
  55.         b->start = ngx_palloc(ctx->pool, size);   
  56.         if (b->start == NULL) {   
  57.             return NGX_ERROR;   
  58.         }   
  59.     }   
  60.   
  61.     b->pos = b->start;   
  62.     b->last = b->start;   
  63.     b->end = b->last + size;   
  64. //设置temporary.   
  65.     b->temporary = 1;   
  66.     b->tag = ctx->tag;   
  67.     b->recycled = recycled;   
  68.   
  69.     ctx->buf = b;   
  70. //更新allocated,可以看到每分配一个就加1.  
  71.     ctx->allocated++;   
  72.   
  73.     return NGX_OK;   
  74. }  


然后接下来这部分就是复制buf,然后调用filter链进行发送。

Java代码 复制代码 收藏代码
  1. //复制buf.   
  2. rc = ngx_output_chain_copy_buf(ctx);   
  3.   
  4.             if (rc == NGX_ERROR) {   
  5.                 return rc;   
  6.             }   
  7. //如果返回AGAIn,一般来说不会返回这个值的.   
  8.             if (rc == NGX_AGAIN) {   
  9.                 if (out) {   
  10.                     break;   
  11.                 }   
  12.   
  13.                 return rc;   
  14.             }   
  15.   
  16.             /* delete the completed buf from the ctx->in chain */  
  17. //如果ctx->in中处理完毕的buf则删除当前的buf  
  18.             if (ngx_buf_size(ctx->in->buf) == 0) {   
  19.                 ctx->in = ctx->in->next;   
  20.             }   
  21.   
  22.             cl = ngx_alloc_chain_link(ctx->pool);   
  23.             if (cl == NULL) {   
  24.                 return NGX_ERROR;   
  25.             }   
  26. //链接chain到out.   
  27.             cl->buf = ctx->buf;   
  28.             cl->next = NULL;   
  29.             *last_out = cl;   
  30.             last_out = &cl->next;   
  31.             ctx->buf = NULL;   
  32.         }   
  33.   
  34.         if (out == NULL && last != NGX_NONE) {   
  35.   
  36.             if (ctx->in) {   
  37.                 return NGX_AGAIN;   
  38.             }   
  39.   
  40.             return last;   
  41.         }   
  42. //调用filter链   
  43.         last = ctx->output_filter(ctx->filter_ctx, out);   
  44.   
  45.         if (last == NGX_ERROR || last == NGX_DONE) {   
  46.             return last;   
  47.         }   
  48. //update chain,这里主要是将处理完毕的chain放入到free,没有处理完毕的放到busy中.  
  49.         ngx_chain_update_chains(&ctx->free, &ctx->busy, &out, ctx->tag);   
  50.         last_out = &out;  


ngx_chain_update_chains这个函数我以前的blog有分析过,想了解的,可以看我前面的blog
原创粉丝点击