nginx释放请求与tcp连接
2017-07-08 06:10
369 查看
前面的文章分析了nginx服务器如何接收http请求行、请求头部、然后调用各个模块共同协作处理请求、以及接收包体等过程。现在来分析下nginx服务器是如何结束一个http请求的(关闭http请求与tcp连接)。结束一个http请求的过程就复杂多了,在结束一个请求过程中,nginx考虑了各种可能出现的场景。例如: (1)在客户端访问的文件不存在时,如果指定了error page指令,则在请求结束时发送这个特殊的错误页面给客户端; (2)如果在给客户端发送响应时,一次调度并没有发送完所有的数据,则会重新注册请求的写回调,以便再次调度执行时,能够继续向客户端发送响应数据;
(3)如果原始请求的的引用计数不为0时,不会真正释放请求,等待引用计数为0时才释放。
用于释放一个http请求的接口很多,但http框架只提供了2个释放请求的接口给http模块调用, ngx_http_finalize_request用于正常结束一个请求; 而ngx_http_terminate_request用于强制终止一个请求,不管当前是否还在读取来自客户端的数据。其它接口都是http框架内部使用的,http模块不能调用。下面看下正常结束一个http请求的函数调用关系:
ngx_http_finalize_request函数用于正常结束一个http请求,这是大部分http模块会调用的函数。图中越往上的是高层接口,ngx_http_close_request函数内会调用两个最底层函数,一个用于释放一个请求,另一个用于释放一个连接。还是按照从底层往上来分析如何结束一个http请求并释放tcp连接吧!
一、调用底层接口释放请求
二、keepalive与延迟关闭机制
ngx_http_finalize_connection函数是一个比ngx_http_close_request更上一层的接口。函数内部最后也会调用ngx_http_close_request来关闭请求。 但这个函数还会判断是否开启了keeaplive机制以及开启了延迟关闭机制。如果开启了这些机制,则http请求会马上被关闭,但这个请求之上的tcp连接则不会马上被关闭。关于keepalive机制与延迟关闭机制,还是比较重要的,打算分别用两篇文章来分析,这几天会陆续发布上来。这里只是大概提下,就不详细分析了。
三、http框架接口ngx_http_finalize_request
这是http框架提供的接口,各个http模块在执行完某个操作都需要调用这个函数,来把请求的引用计数减去1,当引用计数为0时才会真正释放一个请求。这个函数实现比较复杂,考虑了各种场景,我们分别来看下这些场景。
(1)当执行某个操作结束后,例如把读事件从epoll红黑树中删除时。 这个操作执行完成后,会调用这个函数,表示这个操作已经完成了,需要把这个操作对应的引用计数给减去1。这种情况大多是传递NGX_NODE参数。
当nginx服务器检查到客户端发的请求有问题时,将会使用ngx_http_terminate_request强制终止一个请求, 这也是由框架提供的函数, http模块可以调用这个函数来强制结束一个请求。看下这个函数的调用关系:
可以看到,最终也是调用ngx_http_close_request用来关闭一个http请求与tcp连接的。函数会把引用计算强制修改为1,而不管是否还有其他模块在处理这个请求,然后调用ngx_http_close_request
(3)如果原始请求的的引用计数不为0时,不会真正释放请求,等待引用计数为0时才释放。
用于释放一个http请求的接口很多,但http框架只提供了2个释放请求的接口给http模块调用, ngx_http_finalize_request用于正常结束一个请求; 而ngx_http_terminate_request用于强制终止一个请求,不管当前是否还在读取来自客户端的数据。其它接口都是http框架内部使用的,http模块不能调用。下面看下正常结束一个http请求的函数调用关系:
ngx_http_finalize_request函数用于正常结束一个http请求,这是大部分http模块会调用的函数。图中越往上的是高层接口,ngx_http_close_request函数内会调用两个最底层函数,一个用于释放一个请求,另一个用于释放一个连接。还是按照从底层往上来分析如何结束一个http请求并释放tcp连接吧!
一、调用底层接口释放请求
//关闭一个请求(将引用计数减去1,如果引用计数为0则会真正的关闭请求,否则不会关闭请求) static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc) { //在用释放请求时,先把引用计数减1,然后引用计数还不为0则说明可能还有其他http模块需要 //使用到这个请求,此时还不能释放请求,函数直接返回。 r->count--; if (r->count || r->blocked) { return; } //执行到这里说明真正需要关闭请求了,则会释放请求对象 ngx_http_free_request(r, rc); //用于关闭tcp连接,并释放这个连接上的资源 ngx_http_close_connection(c); }调用ngx_http_close_request这个函数有可能并不会马上关闭这个请求,因为其它模块还在使用这个请求时,如果此时释放了,将会导致内存访问越界致命错误。用于释放请求与释放连接的两个底层函数都比较简单,这里就不贴代码了。当然,如果引用计数为0了,则此时是真正要释放请求与连接了。
二、keepalive与延迟关闭机制
ngx_http_finalize_connection函数是一个比ngx_http_close_request更上一层的接口。函数内部最后也会调用ngx_http_close_request来关闭请求。 但这个函数还会判断是否开启了keeaplive机制以及开启了延迟关闭机制。如果开启了这些机制,则http请求会马上被关闭,但这个请求之上的tcp连接则不会马上被关闭。关于keepalive机制与延迟关闭机制,还是比较重要的,打算分别用两篇文章来分析,这几天会陆续发布上来。这里只是大概提下,就不详细分析了。
//释放http请求与连接 static void ngx_http_finalize_connection(ngx_http_request_t *r) { if (r->main->count != 1) { //如果是丢弃包体,则设置回调 if (r->discard_body) { r->read_event_handler = ngx_http_discarded_request_body_handler; ngx_add_timer(r->connection->read, clcf->lingering_timeout); //设置强制关闭连接的时间,避免一直处理丢弃包体操作 //如果超过了这个时间,则http框架不在接收包体后再执行丢弃包体,直接关闭 if (r->lingering_time == 0) { r->lingering_time = ngx_time() + (time_t) (clcf->lingering_time / 1000); } } //关闭连接 ngx_http_close_request(r, 0); return; } //执行到这里,引用计数为1,则要准备结束请求了 //keepalive为1表示请求需要释放,但tcp连接还是用复用的 if (!ngx_terminate && !ngx_exiting && r->keepalive && clcf->keepalive_timeout > 0) { ngx_http_set_keepalive(r); return; } //指向到这里说明keepavlive为0,则需要关闭http请求与tcp连接,到还需要判断是否需要延迟关闭 //如果指定了总是延迟关闭或者等待客户端发完数据在关闭时,则延迟关闭tcp连接 if (clcf->lingering_close == NGX_HTTP_LINGERING_ALWAYS || (clcf->lingering_close == NGX_HTTP_LINGERING_ON && (r->lingering_close || r->header_in->pos < r->header_in->last || r->connection->read->ready))) { ngx_http_set_lingering_close(r); return; } //执行到这里说明没有延时关闭,也没有keepalive机制,则调用底层函数关闭连接 ngx_http_close_request(r, 0); }
三、http框架接口ngx_http_finalize_request
这是http框架提供的接口,各个http模块在执行完某个操作都需要调用这个函数,来把请求的引用计数减去1,当引用计数为0时才会真正释放一个请求。这个函数实现比较复杂,考虑了各种场景,我们分别来看下这些场景。
(1)当执行某个操作结束后,例如把读事件从epoll红黑树中删除时。 这个操作执行完成后,会调用这个函数,表示这个操作已经完成了,需要把这个操作对应的引用计数给减去1。这种情况大多是传递NGX_NODE参数。
//由各个http模块调用的,释放http请求的函数 void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { //http请求中某一个动作结束,而请求还有其他的业务要处理,多半是传递NGX_DONE参数, //函数内部在引用计数为0时才会销毁请求。 if (rc == NGX_DONE) { ngx_http_finalize_connection(r); return; } }(2)nginx服务器在收到来自客户的请求行与请求头后,会调用http各个模块进行处理。这些模块介入到http处理的11个阶段中。如果一次操作没有执行完这11个阶段,则需要重新执行以便完成剩余的阶段。
void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { //表示请求还需要按照11个http阶段继续处理下去 if (rc == NGX_DECLINED) { //设置空,目的是为了在NGX_HTTP_CONTENT_PHASE阶段调用其它介入模块的处理方法。 //在这个阶段是很多http模块都愿意介入的阶段。将这个指针清空,目的是为了调用这些模块的处理方法 r->content_handler = NULL; r->write_event_handler = ngx_http_core_run_phases; //继续剩余的11个节点处理 ngx_http_core_run_phases(r); return; } }为什么需要把请求对象的content_handler指针清空。因为在内容处理阶段,每一个location只能有一个模块设置content_handler回调,但是可以有很多模块设置全局的回调。nginx优先使用这个content_handler回调。看下ngx_http_core_content_phase函数就明白了。
//NGX_HTTP_CONTENT_PHASE阶段的checker方法 ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph) { //优先使用location内的content_handler回调 if (r->content_handler) { ngx_http_finalize_request(r, r->content_handler(r)); return NGX_OK; } //在content_handler方法不存在时,才使用各个模块提供的全局的回调 rc = ph->handler(r); }(3)在客户端访问的文件不存在时,如果指定了error page选项,则在请求结束时会由http框架构造300以上的错误码,并发送这个特殊的错误页面给客户端浏览器。或者客户端上传文件到服务器成功后,也需要http框架构造201或者204响应码给客户端。
void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { //如果http模块需要http框架构造并发送>=300以上的响应码给客户端 //或者http模块要发送上传文件成功的201,204响应码 if (rc >= NGX_HTTP_SPECIAL_RESPONSE || rc == NGX_HTTP_CREATED || rc == NGX_HTTP_NO_CONTENT) { //发送响应码页面 ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc)); return; } }(4)在给客户端发送http响应时,如果一次调度并不能马上发送完所有的响应数据,则会设置请求对象的写回调为ngx_http_writer,下一次写事件被调度执行时,会继续往客户端浏览器发送剩余的http响应内容。
void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { //执行到这里,说明是原始请求 //一次调度没有全部发送完http应答,则重新注册写事件回调以便下次调度时再次发生 if (r->buffered || c->buffered || r->postponed || r->blocked) { ngx_http_set_write_handler(r)); return; } } //如果写数据一次性没写完,则重新注册写回调 static ngx_int_t ngx_http_set_write_handler(ngx_http_request_t *r) { r->read_event_handler = r->discard_body ? ngx_http_discarded_request_body_handler: ngx_http_test_reading; //写事件回调,下线调度时继续往客户端发送包体 r->write_event_handler = ngx_http_writer; //注册写事件到epoll ngx_handle_write_event(wev, clcf->send_lowat)); return NGX_OK; }(5)子请求相关逻辑也是一块比较复杂些的逻辑,将会有专门的文章分析,这里就不在分析了。到此,需要正常的关闭http请求了,但会不会马上就关闭请求要看引用计数,引用计数为0则会关闭请求,但并不一定会马上关闭tcp连接,因为有可能开启了keepalive机制或者延迟关闭机制。也就是说引用计数决定是否需要关闭http请求,而keepalive机制或者延迟关闭机制则决定是否需要关闭tcp连接。
void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { //执行到这里,表示正在要释放请求了,下面的操作都是释放处理 r->done = 1; r->write_event_handler = ngx_http_request_empty_handler; //客户端浏览器发送了FIN数据包 if (c->read->eof) { ngx_http_close_request(r, 0); return; } //正常关闭流程(是否关闭请求看引用计数是否为0,是否tcp连接要看是否开启了 //keepalive或者延迟关闭机制) ngx_http_finalize_connection(r); }四、异常时强制关闭请求
当nginx服务器检查到客户端发的请求有问题时,将会使用ngx_http_terminate_request强制终止一个请求, 这也是由框架提供的函数, http模块可以调用这个函数来强制结束一个请求。看下这个函数的调用关系:
可以看到,最终也是调用ngx_http_close_request用来关闭一个http请求与tcp连接的。函数会把引用计算强制修改为1,而不管是否还有其他模块在处理这个请求,然后调用ngx_http_close_request
//强制终止一个请求,被http模块调用 static void ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc) { if (mr->write_event_handler) { mr->write_event_handler = ngx_http_terminate_handler; return; } } //将引用计数改为1,然后调用ngx_http_close_request关闭http请求与tcp连接 static void ngx_http_terminate_handler(ngx_http_request_t *r) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http terminate handler count:%d", r->count); r->count = 1; ngx_http_close_request(r, 0); }到此为止释放一个http请求与tcp连接已经分析完成了, 写这篇文章的时候还真的挺累,贴了一堆的代码上来。说实话,真心不想贴太多的代码,不想过多的纠缠于代码细节。但nginx服务器在释放请求这块逻辑上考虑了很多的场景, 各种条件判断语句看了都有点晕。实在找不出一种方法来把这些复杂的问题给表现出来, 因此对于每一种场景,都提供一个代码片段来帮助尽量帮助大家理解吧! 不知读者有没更好的建议,怎么把这种复杂问题给简单表现出来,方便大家理解。
相关文章推荐
- 查看Web服务器(Nginx Apache)的并发请求数及其TCP连接状态
- 查看Web服务器(NginxApache)的并发请求数及其TCP连接状态
- nginx限制客户端请求数+iptables限制TCP连接和频率来防止DDOS
- 查看Apache并发请求数及其TCP连接状态
- Apache并发请求数及其TCP连接状态查看的方法
- 查看Apache并发请求数及其TCP连接状态[转]
- 查看Apache并发请求数及其TCP连接状态
- 查看Apache并发请求数及其TCP连接状态(http://www.duyu.name/archives/62)
- LINUX下查看Apache并发请求数及其TCP连接状态
- 查看Apache并发请求数及其TCP连接状态及用tcpdump来收集ip的访问量
- 呼入连接请求队列-TCP
- 查看Apache并发请求数及其TCP连接状态
- 查看Apache并发请求数及其TCP连接状态
- Apache并发请求数及其TCP连接状态故障排除
- TCP连接的建立(三次握手)和释放(四次握手)
- tcp/ip Incoming Connection Request Queue(呼入连接请求队列)
- Linux中查看Apache的并发请求数及其TCP连接状态
- LINUX下查看Apache并发请求数及其TCP连接状态
- 查看Apache并发请求数及其TCP连接状态
- 命令查看Apache的并发请求数及TCP连接状态