nginx使用output chain来发送数据,这里主要有两个原因:
1) 数据可能并不是一次性产生,而是分散在多个步骤,这样客观上形成了一个等待发送的链式数据;
2) 数据量太大,单个小块内存存放不下,并且一次也发送不完
因此,这里我们首先需要以一个链式结构保存起要发送的数据,其次我们还需要保存发送上下文,我们用到前面介绍的
typedef struct ngx_output_chain_ctx_s ngx_output_chain_ctx_t;
结构。
1. 相关静态函数声明
这里声明了一些output chain发送的辅助函数:
-
函数ngx_output_chain_as_is(): 主要用来判断是否需要复制buf。如果返回值为1,表示不需要拷贝; 否则表示需要拷贝
-
函数ngx_output_chain_aio_setup(): 建立aio来发送文件,当前我们并不支持此功能(即 ‘NGX_HAVE_AIO_SENDFILE’宏未定义)
-
函数ngx_output_chain_add_copy(): 拷贝in
到chain
中
-
函数ngx_output_chain_align_file_buf(): 此函数主要用于对齐file buf,某些发送函数需要进行相应对齐。
-
函数ngx_output_chain_get_buf(): 获得指定大小的buf,赋值给ctx->buf
-
函数ngx_output_chain_copy_buf(): 拷贝数据。我们一般输出的话都是从ctx->in直接拷贝到ctx->buf中,然后发送出去
2. 函数ngx_output_chain()
此函数的目的是发送in
中的数据,ctx
用来保存发送的上下文。因为通常情况下,不能一次发送完成。nginx因为使用了ET
模式,在网络编程事件管理上简单了,但是编程中处理事件复杂了,需要不停的循环做处理; 事件的函数回调,次数也不确定,因此需要使用context上下文对象来保存发送到什么环节了。
此函数当前只在如下两个模块中会调用到:
下面我们分成几个部分来讲解本函数
1) 简单发送(short path)
这里我们看到假如ctx->in
及ctx->busy
均为NULL时,说明当前ctx中并未有太多的数据需要发送。此时如果参数in
传递进来的要发送的数据也较少,那么就可以直接调用ctx->output_filter()来进行处理。也就是说当能直接确定所有in chain都不需要复制时,可以直接调用output_filter来交给剩下的filter去处理。
2) 将传递进来的in chain拷贝到ctx->in中
这里将参数传递进来的in chain buf拷贝到ctx->in的结尾,以等待后续的发送处理;
3) 处理主流程
主流程所完成的基本功能是: 将in
中的数据处理后放到ctx->buf临时缓冲链中,然后再调用ctx->output_filter对该临时buf进行后续的发送处理。没有处理完成的chain放到ctx->busy中,而已经发送完毕的就放到ctx->free中。
在这里主处理逻辑阶段,nginx做的非常巧妙也非常复杂,首先是chain的重用,然后是buf的重用。
3.1) chain的重用
这里我们首先来看chain的重用,涉及到的几个关键的结构及域: ctx->free
、ctx->busy
、ctx->pool->chain
。其中每次没有处理完的chain:
ngx_buf_size(cl->buf) != 0
会被放到ctx->busy
链中; 而已经处理完成chain会被放到ctx->free
中(注:若tag
已经修改了,则会调用ngx_free_chain()来将该chain直接放入到ctx->pool->chain
中)。调用ngx_alloc_chain_link()时(参见core/ngx_buf.c中):
上面我们看到如果pool->chain中存在chain的话,就不用malloc了,而是直接返回pool->chain。
3.2) buf重用
严格意义上来说,buf的重用是从free中的chain中取得的,当free中的buf被重用,则这个buf对应的chain就会被链接到ctx->pool中,从而这个chain就会被重用。也就是说首先考虑的是buf的重用,只有当这个chain的buf确定不需要被重用的时候,chain才会被链接到ctx->pool中被重用。
另外还有一个就是ctx->allocated
域,这个field表示了当前的上下文中已经分配了多少个buf,output_buffer
命令用来设置output的buf的大小以及buf的个数。而allocated
如果比output_buffer
大的话,则需要先处理完已存在的buf,然后才能重新分配buf。
接下来我们分析代码,上面所说的重用以及buf的控制,代码里面都可以看的比较清晰,下面这段主要是拷贝buf前所做的一些工作,比如判断是否拷贝,以及给buf
分配内存等,然后将整理好的数据交给output_filter
来进行下一步处理(及发送):
3. 函数ngx_output_chain_as_is()
此函数主要用于判定是否需要复制buf。如果返回值为1,则说明不需要复制,否则说明需要复制。上面我们注意两个标记:
4. 函数ngx_output_chain_aio_setup()
当前我们并不支持NGX_HAVE_ASIO_SENDFILE
,这里不做介绍。
5. 函数ngx_output_chain_add_copy()
本函数用于将in
中的数据拷贝到chain
中。下面我们简要分析一下本函数:
6. 函数ngx_output_chain_align_file_buf()
本函数用于对齐file buf,因为使用directio发送file buf时,会有对齐方面的要求。下面我们简单分析下本函数的实现:
7. 函数ngx_output_chain_get_buf()
本函数用于分配一块指定大小的空间给ctx->buf。下面简要分析一下本函数:
8. 函数ngx_output_chain_copy_buf()
本函数用于将ctx->in
中的buf拷贝到ctx->buf
这样一个临时缓存中进行处理。下面我们简要分析:
9. 函数ngx_chain_writer()
此函数是用来真正调用connection
来将chain中的数据发送出去。下面我们简要分析一下该函数的实现:
[参看]
-
ngx_output_chain 函数分析
-
Nginx filter分析)
-
Nginx filter 模块解析(filter调用顺序)
-
nginx处理post之转发
-
nginx HTTP处理流程
-
nginx的十一个阶段处理
-
Development Guide
-
nginx phase handler的原理和选择
-
nginx模块执行顺序分析
-
Emiller’s Guide To Nginx Module Development