nginx的filter的处理

@using CSDN.BLOG.Model@using CSDN.BLOG.Helper@using CSDN.Common

推荐专家

  • @{ var list = PeopleHelper.GetRecommend(false); if (list != null) {Html.RenderPartial("_peopleview2", list); } }
文章分类: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. }  
static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;

static ngx_int_t
ngx_http_gzip_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_gzip_header_filter;

    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_gzip_body_filter;

    return NGX_OK;
}



这里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. }  
ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
    if (r->err_status) {
        r->headers_out.status = r->err_status;
        r->headers_out.status_line.len = 0;
    }

    return ngx_http_top_header_filter(r);
}


可以看到当发送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_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    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);

//启动body filter。
    rc = ngx_http_top_body_filter(r, in);
..............................................................

    return rc;
}


这里还有一个问题,那就是最后一个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.     }  
for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;

//如果存在postconfiguratio则调用初始化。
        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }


代码很简单就是遍历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_module_t *ngx_modules[] = {
 ..............................................................
    &ngx_http_write_filter_module,
    &ngx_http_header_filter_module,
........................................................................
    NULL
};


可以看到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)  
ngx_int_t
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;  
  
 off_t                      size, sent, nsent, limit;
    ngx_uint_t                 last, flush;
    ngx_msec_t                 delay;
    ngx_chain_t               *cl, *ln, **ll, *chain;
    ngx_connection_t          *c;
    ngx_http_core_loc_conf_t  *clcf;

//得到当前所属的连接
    c = r->connection;

    if (c->error) {
        return NGX_ERROR;
    }

    size = 0;
    flush = 0;
    last = 0;
//得到上次没有发送完毕的chain
    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.     }  
 
 for (cl = r->out; cl; cl = cl->next) {
        ll = &cl->next;

#if 1
//如果有0长度的buf则返回错误。
        if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
 ......................................................................

            ngx_debug_point();
            return NGX_ERROR;
        }
#endif

//得到buf的大小
        size += ngx_buf_size(cl->buf);
//看当传输完毕后是否要刷新buf。
        if (cl->buf->flush || cl->buf->recycled) {
            flush = 1;
        }
//看是否是最后一个buf
        if (cl->buf->last_buf) {
            last = 1;
        }
    }


接下来这部分是用来链接新的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.     }  
for (ln = in; ln; ln = ln->next) {
//
        cl = ngx_alloc_chain_link(r->pool);
        if (cl == NULL) {
            return NGX_ERROR;
        }

        cl->buf = ln->buf;
//前面的代码我们知道ll已经指向out chain的最后一个位置了,因此这里就是将新的chain链接到out chain的后面。
        *ll = cl;
        ll = &cl->next;
#if 1
//校验buf
        if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {

            ngx_debug_point();
            return NGX_ERROR;
        }
#endif

//计算大小
        size += ngx_buf_size(cl->buf);

//判断是否需要flush
        if (cl->buf->flush || cl->buf->recycled) {
            flush = 1;
        }

//判断是否是最后一个buf
        if (cl->buf->last_buf) {
            last = 1;
        }
    }


然后接下来的这段代码主要是对进行发送前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  
//这个并没有用到
#define NGX_HTTP_LOWLEVEL_BUFFERED         0xf0
//主要是这个,这个表示在最终的write filter中被buffered
#define NGX_HTTP_WRITE_BUFFERED            0x10
//判断是否有被设置
#define NGX_LOWLEVEL_BUFFERED  0x0f

//下面几个filter中被buffered
#define NGX_HTTP_GZIP_BUFFERED             0x20
#define NGX_HTTP_SSI_BUFFERED              0x01
#define NGX_HTTP_SUB_BUFFERED              0x02
#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;  
 clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

//也就是说将要发送的字节数小于postpone_output并且不是最后一个buf,并且不需要刷新chain的话,就直接返回。
    if (!last && !flush && in && size < (off_t) clcf->postpone_output) {
        return NGX_OK;
    }

///如果设置了write的delayed,则设置标记。
    if (c->write->delayed) {
        c->buffered |= NGX_HTTP_WRITE_BUFFERED;
        return NGX_AGAIN;
    }

//如果size为0,并且没有设置buffered标记,则进入清理工作。
    if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED)) {
//如果是最后一个buf,则清理buffered标记然后清理out chain
        if (last) {
            r->out = NULL;
            c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;

            return NGX_OK;
        }

//如果有设置flush的话,则会强行传输当前buf之前的所有buf,因此这里就需要清理out chain。
        if (flush) {
            do {
                r->out = r->out->next;
            } while (r->out);

//清理buf 标记
            c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;

            return NGX_OK;
        }

        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                      "the http output chain is empty");

        ngx_debug_point();

        return NGX_ERROR;
    }

//如果有发送速率限制。
    if (r->limit_rate) {
//计算是否有超过速率限制
        limit = r->limit_rate * (ngx_time() - r->start_sec + 1)
                - (c->sent - clcf->limit_rate_after);
//如果有
        if (limit <= 0) {
//设置delayed标记
            c->write->delayed = 1;
//设置定时器
            ngx_add_timer(c->write,
                          (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1));

//设置buffered。
            c->buffered |= NGX_HTTP_WRITE_BUFFERED;

            return NGX_AGAIN;
        }

    } else if (clcf->sendfile_max_chunk) {
//sendfile所用到的limit。
        limit = clcf->sendfile_max_chunk;

    } else {
        limit = 0;
    }

    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;  
//调用发送函数。
chain = c->send_chain(c, r->out, limit);

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http write filter %p", chain);

    if (chain == NGX_CHAIN_ERROR) {
        c->error = 1;
        return NGX_ERROR;
    }

//控制imit_rate,这个值一般是在nginx.conf中配置的。
    if (r->limit_rate) {

        nsent = c->sent;

        if (clcf->limit_rate_after) {

            sent -= clcf->limit_rate_after;
            if (sent < 0) {
                sent = 0;
            }

            nsent -= clcf->limit_rate_after;
            if (nsent < 0) {
                nsent = 0;
            }
        }

        delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate + 1);

        if (delay > 0) {
            c->write->delayed = 1;
            ngx_add_timer(c->write, delay);
        }

    } else if (c->write->ready
               && clcf->sendfile_max_chunk
               && (size_t) (c->sent - sent)
                      >= clcf->sendfile_max_chunk - 2 * ngx_pagesize)
    {
        c->write->delayed = 1;
        ngx_add_timer(c->write, 1);
    }

//开始遍历上一次还没有传输完毕的chain,如果这次没有传完的里面还有的话,就跳出循环,否则free这个chain
    for (cl = r->out; cl && cl != chain; /* void */) {
        ln = cl;
        cl = cl->next;
        ngx_free_chain(r->pool, ln);
    }

///out chain赋值
    r->out = chain;

//如果chain存在,则设置buffered并且返回again。
    if (chain) {
        c->buffered |= NGX_HTTP_WRITE_BUFFERED;
        return NGX_AGAIN;
    }

//否则清理buffered
    c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;

//如果有其他的filter buffered并且postponed被设置了,则我们返回again,也就是还有buf要处理。
    if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
        return NGX_AGAIN;
    }

//否则返回ok
    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){
		return	NULL;
	}
	
	//一个新的内存池
	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内存池功能强大,所以代码也比较复杂,

这里只

 

 

 

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)  
ngx_chain_t *
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。  
send 表示将要发送的buf已经已经发送的大小。
sent表示已经发送的buf的大小
prev_send 表示上一次发送的大小,也就是已经发送的buf的大小。
fprev 和prev-send类似,只不过是file类型的。

complete表示是否buf被完全发送了,也就是sent是否等于send - prev_send.

header表示需要是用writev来发送的buf。也就是only in memory的buf。

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;  
wev = c->write;

    if (!wev->ready) {
        return in;
    }

    if (limit == 0 || limit > (off_t) (NGX_SENDFILE_LIMIT - ngx_pagesize)) {
        limit = NGX_SENDFILE_LIMIT - ngx_pagesize;
    }


    send = 0;
//设置header,也就是in memory的数组
    header.elts = headers;
    header.size = sizeof(struct iovec);
    header.nalloc = NGX_HEADERS;
    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.         }  
//开始遍历
for (cl = in;
             cl && header.nelts < IOV_MAX && send < limit;
             cl = cl->next)
        {
            if (ngx_buf_special(cl->buf)) {
                continue;
            }
//如果不止是在buf中,这是因为有时in file的文件我们可能需要内存中也有拷贝,所以如果一个buf同时in memoey和in file的话,nginx会认为它是in file的来处理。
            if (!ngx_buf_in_memory_only(cl->buf)) {
                break;
            }

//得到buf的大小
            size = cl->buf->last - cl->buf->pos;

//大于limit的话修改为size
            if (send + size > limit) {
                size = limit - send;
            }
//如果prev等于pos,则说明当前的buf的数据和前一个buf的数据是连续的。
            if (prev == cl->buf->pos) {
                iov->iov_len += (size_t) size;

            } else {
//否则说明是不同的buf,因此add一个iovc。
                iov = ngx_array_push(&header);
                if (iov == NULL) {
                    return NGX_CHAIN_ERROR;
                }

                iov->iov_base = (void *) cl->buf->pos;
                iov->iov_len = (size_t) size;
            }

//这里可以看到prev保存了当前buf的结尾。
            prev = cl->buf->pos + (size_t) size;
//更新发送的大小
            send += size;
        }


然后是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.         }  
//可以看到如果header的大小不为0则说明前面有需要发送的buf,因此我们就跳过in file处理
if (header.nelts == 0 && cl && cl->buf->in_file && send < limit) {
//得到file
            file = cl->buf;

//开始合并。
            do {
//得到大小
                size = cl->buf->file_last - cl->buf->file_pos;

//如果太大则进行对齐处理。
                if (send + size > limit) {
                    size = limit - send;

                    aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
                               & ~((off_t) ngx_pagesize - 1);

                    if (aligned <= cl->buf->file_last) {
                        size = aligned - cl->buf->file_pos;
                    }
                }

//设置file_size.
                file_size += (size_t) size;
//设置需要发送的大小
                send += size;
//和上面的in memory处理一样就是保存这次的last
                fprev = cl->buf->file_pos + size;
                cl = cl->next;

            } while (cl
                     && cl->buf->in_file
                     && send < limit
                     && file->file->fd == cl->buf->file->fd
                     && fprev == cl->buf->file_pos);
        }


然后就是发送部分,这里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.           
if (file) {
#if 1
            if (file_size == 0) {
                ngx_debug_point();
                return NGX_CHAIN_ERROR;
            }
#endif
#if (NGX_HAVE_SENDFILE64)
            offset = file->file_pos;
#else
            offset = (int32_t) file->file_pos;
#endif

//发送数据
            rc = sendfile(c->fd, file->file->fd, &offset, file_size);
......................................................
//得到发送的字节数
            sent = rc > 0 ? rc : 0;

        } else {
            rc = writev(c->fd, header.elts, header.nelts);
.......................................................................

            sent = rc > 0 ? rc : 0;
}
        


接下来这部分就是更新标记的部分,主要是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.         }  
//如果send - prev_send == sent则说明该发送的都发完了。
if (send - prev_send == sent) {
            complete = 1;
        }
//更新congnect的sent域。
        c->sent += sent;

//开始重新遍历chain,这里是为了防止没有发送完全的情况,此时我们就需要切割buf了。
        for (cl = in; cl; cl = cl->next) {

            if (ngx_buf_special(cl->buf)) {
                continue;
            }

            if (sent == 0) {
                break;
            }
//得到buf size
            size = ngx_buf_size(cl->buf);

//如果大于当前的size,则说明这个buf的数据已经被完全发送完毕了。,因此更新它的域。
            if (sent >= size){
//更新sent域
                sent -= size;
//如果在内存则更新pos
                if (ngx_buf_in_memory(cl->buf)) {
                    cl->buf->pos = cl->buf->last;
                }
//如果在file
                if (cl->buf->in_file) {
                    cl->buf->file_pos = cl->buf->file_last;
                }

                continue;
            }

//到这里说明当前的buf只有一部分被发送出去了,因此这里我们只需要修改指针。以便于下次发送。
            if (ngx_buf_in_memory(cl->buf)) {
                cl->buf->pos += (size_t) sent;
            }
//同上。
            if (cl->buf->in_file) {
                cl->buf->file_pos += sent;
            }

            break;
        }


最后一部分就是一些是否退出循环的操作。这里要注意,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. }  
static ngx_int_t
ngx_http_copy_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_copy_filter;

    return NGX_OK;
}


可以看到,它只有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;  
typedef struct {
    ngx_bufs_t  bufs;
} 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. }  
static char *
ngx_http_copy_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_copy_filter_conf_t *prev = parent;
    ngx_http_copy_filter_conf_t *conf = child;

//默认是1个buf,大小为32768字节
    ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, 1, 32768);

    return NULL;
}


由于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;  
typedef struct {
//保存临时的buf
    ngx_buf_t                   *buf;
//保存了将要发送的chain
    ngx_chain_t                 *in;
//保存了已经发送完毕的chain,以便于重复利用
    ngx_chain_t                 *free;
//保存了还未发送的chain
    ngx_chain_t                 *busy;

//sendfile标记
    unsigned                     sendfile:1;
//directio标记
    unsigned                     directio:1;
#if (NGX_HAVE_ALIGNED_DIRECTIO)
    unsigned                     unaligned:1;
#endif
//是否需要在内存中保存一份(使用sendfile的话,内存中没有文件的拷贝的,而我们有时需要处理文件,此时就需要设置这个标记)
    unsigned                     need_in_memory:1;
//是否存在的buf复制一份,这里不管是存在在内存还是文件,后面会看到这两个标记的区别。
    unsigned                     need_in_temp:1;

    ngx_pool_t                  *pool;
//已经allocated的大小
    ngx_int_t                    allocated;
//对应的bufs的大小,这个值就是我们loc conf中设置的bufs
    ngx_bufs_t                   bufs;
//表示现在处于那个模块(因为upstream也会调用output_chain)
    ngx_buf_tag_t                tag;

//这个值一般是ngx_http_next_filter,也就是继续调用filter链
    ngx_output_chain_filter_pt   output_filter;
//当前filter的上下文,这里也是由于upstream也会调用output_chain
    void                        *filter_ctx;
} 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. }  
static ngx_int_t
ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                     rc;
    ngx_connection_t             *c;
    ngx_output_chain_ctx_t       *ctx;
    ngx_http_copy_filter_conf_t  *conf;

    c = r->connection;

//获取ctx
    ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module);

//如果为空,则说明需要初始化ctx
    if (ctx == NULL) {
        conf = ngx_http_get_module_loc_conf(r, ngx_http_copy_filter_module);

        ctx = ngx_pcalloc(r->pool, sizeof(ngx_output_chain_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_copy_filter_module);

//设置对应的域
        ctx->sendfile = c->sendfile;
//可以看到如果我们给request设置filter_need_in_memory的话,ctx的这个域就会被设置
        ctx->need_in_memory = r->main_filter_need_in_memory
                              || r->filter_need_in_memory;
//和上面类似
        ctx->need_in_temp = r->filter_need_temporary;

        ctx->pool = r->pool;
        ctx->bufs = conf->bufs;
        ctx->tag = (ngx_buf_tag_t) &ngx_http_copy_filter_module;
//可以看到output_filter就是body filter的next
        ctx->output_filter = (ngx_output_chain_filter_pt) ngx_http_next_filter;
//此时filter ctx为当前的请求
        ctx->filter_ctx = r;

        r->request_output = 1;
    }
//最关键的函数,下面会详细分析。
    rc = ngx_output_chain(ctx, in);
......................................................

    return rc;
}


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

Java代码 复制代码  收藏代码
  1. ngx_int_t   
  2. ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in)  
ngx_int_t
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.     }  
 if (ctx->in == NULL && ctx->busy == NULL) {

//下面的注释解释的很详细
        /*
         * the short path for the case when the ctx->in and ctx->busy chains
         * are empty, the incoming chain is empty too or has the single buf
         * that does not require the copy
         */

        if (in == NULL) {
            return ctx->output_filter(ctx->filter_ctx, in);
        }

//这里说明只有一个chain,并且它的buf不需要复制
        if (in->next == NULL
#if (NGX_SENDFILE_LIMIT)
            && !(in->buf->in_file && in->buf->file_last > NGX_SENDFILE_LIMIT)
#endif
            && ngx_output_chain_as_is(ctx, in->buf))
        {
            return ctx->output_filter(ctx->filter_ctx, in);
        }
    }


上面我们看到了一个函数 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. }  
static ngx_inline ngx_int_t
ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf)
{
    ngx_uint_t  sendfile;

//是否为specialbuf,是的话返回1,也就是不用拷贝
    if (ngx_buf_special(buf)) {
        return 1;
    }

//如果buf在文件中,并且使用了directio的话,需要拷贝buf
    if (buf->in_file && buf->file->directio) {
        return 0;
    }

//sendfile标记
    sendfile = ctx->sendfile;

#if (NGX_SENDFILE_LIMIT)
//如果pos大于sendfile的限制,设置标记为0
    if (buf->in_file && buf->file_pos >= NGX_SENDFILE_LIMIT) {
        sendfile = 0;
    }

#endif

    if (!sendfile) {
//此时如果buf不在内存中,则我们就需要复制到内存一份。
        if (!ngx_buf_in_memory(buf)) {
            return 0;
        }
//否则设置in_file为0.
        buf->in_file = 0;
    }

//如果需要内存中有一份拷贝,而并不在内存中,此时返回0,表示需要拷贝
    if (ctx->need_in_memory && !ngx_buf_in_memory(buf)) {
        return 0;
    }

//如果需要内存中有拷贝,并且存在于内存中或者mmap中,则返回0.
    if (ctx->need_in_temp && (buf->memory || buf->mmap)) {
        return 0;
    }

    return 1;
}


上面有两个标记要注意,一个是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.     }  
if (in) {
//复制到ctx->in中.
        if (ngx_output_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) {
            return NGX_ERROR;
        }
    }



然后就是主要的逻辑处理阶段。这里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. }  
//链接cl到pool->chain中
#define ngx_free_chain(pool, cl)                                             \
    cl->next = pool->chain;                                                  \
    pool->chain = cl

ngx_chain_t *
ngx_alloc_chain_link(ngx_pool_t *pool)
{
    ngx_chain_t  *cl;

    cl = pool->chain;
//如果cl存在,则直接返回cl
    if (cl) {
        pool->chain = cl->next;
        return cl;
    }
//否则才会malloc chain
    cl = ngx_palloc(pool, sizeof(ngx_chain_t));
    if (cl == NULL) {
        return NULL;
    }

    return cl;
}


然后是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.     }  
//out为我们最终需要传输的chain,也就是交给剩下的filter处理的chain
 out = NULL;
//last_out为out的最后一个chain
    last_out = &out;
    last = NGX_NONE;

for ( ;; ) {

//开始遍历chain
        while (ctx->in) {

//取得当前chain的buf大小
            bsize = ngx_buf_size(ctx->in->buf);

//跳过bsize为0的buf
            if (bsize == 0 && !ngx_buf_special(ctx->in->buf)) {
                ngx_debug_point();

                ctx->in = ctx->in->next;

                continue;
            }

//判断是否需要复制buf
            if (ngx_output_chain_as_is(ctx, ctx->in->buf)) {

                /* move the chain link to the output chain */
//如果不需要复制,则直接链接chain到out,然后继续循环
                cl = ctx->in;
                ctx->in = cl->next;

                *last_out = cl;
                last_out = &cl->next;
                cl->next = NULL;

                continue;
            }

//到达这里,说明我们需要拷贝buf,这里buf最终都会被拷贝进ctx->buf中,因此这里先判断ctx->buf是否为空
            if (ctx->buf == NULL) {

//如果为空,则取得buf,这里要注意,一般来说如果没有开启directio的话,这个函数都会返回NGX_DECLINED的(具体实现可以去看这个函数的代码)。
                rc = ngx_output_chain_align_file_buf(ctx, bsize);

                if (rc == NGX_ERROR) {
                    return NGX_ERROR;
                }

//大部分情况下,都会落入这个分支
                if (rc != NGX_OK) {

//准备分配buf,首先在free中寻找可以重用的buf
                    if (ctx->free) {

                        /* get the free buf */
//得到free buf
                        cl = ctx->free;
                        ctx->buf = cl->buf;
                        ctx->free = cl->next;
//将要重用的chain链接到ctx->poll中,以便于chain的重用.
                        ngx_free_chain(ctx->pool, cl);

                    } else if (out || ctx->allocated == ctx->bufs.num) {
//如果已经等于buf的个数限制,则跳出循环,发送已经存在的buf.这里可以看到如果out存在的话,nginx会跳出循环,然后发送out,等发送完会再次处理,这里很好的体现了nginx的流式处理
                        break;

                    } else if (ngx_output_chain_get_buf(ctx, bsize) != NGX_OK) {
//这个函数也比较关键,它用来取得buf.我们接下来会详细看这个函数
                        return NGX_ERROR;
                    }
                }
            }
............................................................
    }


上面的代码分析的时候有个很关键的函数,那就是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. }  
static ngx_int_t
ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx, off_t bsize)
{
    size_t       size;
    ngx_buf_t   *b, *in;
    ngx_uint_t   recycled;

    in = ctx->in->buf;
//可以看到这里分配的buf,每个的大小都是我们在nginx.conf中设置的size
    size = ctx->bufs.size;
//默认有设置recycled域.
    recycled = 1;
//如果当前的buf是属于最后一个chain的时候。这里我们要特殊处理。
    if (in->last_in_chain) {
//这边注释很详细,我就不解释了.
        if (bsize < (off_t) size) {

            /*
             * allocate a small temp buf for a small last buf
             * or its small last part
             */
            size = (size_t) bsize;
            recycled = 0;

        } else if (!ctx->directio
                   && ctx->bufs.num == 1
                   && (bsize < (off_t) (size + size / 4)))
        {
            /*
             * allocate a temp buf that equals to a last buf,
             * if there is no directio, the last buf size is lesser
             * than 1.25 of bufs.size and the temp buf is single
             */

            size = (size_t) bsize;
            recycled = 0;
        }
    }
//开始分配buf内存.
    b = ngx_calloc_buf(ctx->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    if (ctx->directio) {
//directio需要对齐

        b->start = ngx_pmemalign(ctx->pool, size, NGX_DIRECTIO_BLOCK);
        if (b->start == NULL) {
            return NGX_ERROR;
        }

    } else {
//大部分情况会走到这里.
        b->start = ngx_palloc(ctx->pool, size);
        if (b->start == NULL) {
            return NGX_ERROR;
        }
    }

    b->pos = b->start;
    b->last = b->start;
    b->end = b->last + size;
//设置temporary.
    b->temporary = 1;
    b->tag = ctx->tag;
    b->recycled = recycled;

    ctx->buf = b;
//更新allocated,可以看到每分配一个就加1.
    ctx->allocated++;

    return NGX_OK;
}


然后接下来这部分就是复制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;  
//复制buf.
rc = ngx_output_chain_copy_buf(ctx);

            if (rc == NGX_ERROR) {
                return rc;
            }
//如果返回AGAIn,一般来说不会返回这个值的.
            if (rc == NGX_AGAIN) {
                if (out) {
                    break;
                }

                return rc;
            }

            /* delete the completed buf from the ctx->in chain */
//如果ctx->in中处理完毕的buf则删除当前的buf
            if (ngx_buf_size(ctx->in->buf) == 0) {
                ctx->in = ctx->in->next;
            }

            cl = ngx_alloc_chain_link(ctx->pool);
            if (cl == NULL) {
                return NGX_ERROR;
            }
//链接chain到out.
            cl->buf = ctx->buf;
            cl->next = NULL;
            *last_out = cl;
            last_out = &cl->next;
            ctx->buf = NULL;
        }

        if (out == NULL && last != NGX_NONE) {

            if (ctx->in) {
                return NGX_AGAIN;
            }

            return last;
        }
//调用filter链
        last = ctx->output_filter(ctx->filter_ctx, out);

        if (last == NGX_ERROR || last == NGX_DONE) {
            return last;
        }
//update chain,这里主要是将处理完毕的chain放入到free,没有处理完毕的放到busy中.
        ngx_chain_update_chains(&ctx->free, &ctx->busy, &out, ctx->tag);
        last_out = &out;


ngx_chain_update_chains这个函数我以前的blog有分析过,想了解的,可以看我前面的blog
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值