zoukankan      html  css  js  c++  java
  • http 怎样关闭

      如何优雅的关闭关闭这个fd , 如果只是一个简单的fd 直接调用close 就行, 但是如果要是一个框架 那就接到 资源回收复用 内存泄漏等问题;

    来看看 ngx 是用怎样的思路处理 事务结束动作;

      每个HTTP请求都有一个引用计数,每派生出一种新的会独立向事件收集器注册事件的动作时(如ngx_http_read_ client_request_body方法或者ngx_http_subrequest方法),都会把引用计数加1,这样每个动作结束时都通过调用ngx_http_finalize_request方法来结束请求,而ngx_http_finalize_request方法实际上却会在引用计数减1后先检查引用计数的值,如果不为O是不会真正销毁请求的。

    void
    ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) 
    {//subrequest注意ngx_http_run_posted_requests与ngx_http_postpone_filter ngx_http_finalize_request配合阅读
        ngx_connection_t          *c;
        ngx_http_request_t        *pr;
        ngx_http_core_loc_conf_t  *clcf;
    
        c = r->connection;
    
        ngx_log_debug7(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "http finalize request rc: %d, "%V?%V" a:%d, c:%d, b:%d, p:%p",
                       rc, &r->uri, &r->args, r == c->data, r->main->count, (int)r->buffered, r->postponed);
    
        /*
        NGX_DONE参数表示不需要做任何事,直接调用ngx_http_finalize_connection方法,之后ngx_http_finalize_request方法结束。当某一种动作
    (如接收HTTP请求包体)正常结束而请求还有业务要继续处理时,多半都是传递NGX_DONE参数。这个ngx_http_finalize_connection方法还会去检
    查引用计数情况,并不一定会销毁请求。
         */
        if (rc == NGX_DONE) {
            ngx_http_finalize_connection(r);  
            return;
        }
    
        if (rc == NGX_OK && r->filter_finalize) {
            c->error = 1;
        }
    
        /*
       NGX_DECLINED参数表示请求还需要按照11个HTTP阶段继续处理下去,这时需要继续调用ngx_http_core_run_phases方法处理请求。这
    一步中首先会把ngx_http_request_t结构体的write—event handler设为ngx_http_core_run_phases方法。同时,将请求的content_handler成员
    置为NULL空指针,它是一种用于在NGX_HTTP_CONTENT_PHASE阶段处理请求的方式,将其设置为NULL足为了让ngx_http_core_content_phase方法
    可以继续调用NGX_HTTP_CONTENT_PHASE阶段的其他处理方法。
         */
        if (rc == NGX_DECLINED) {
            r->content_handler = NULL;
            r->write_event_handler = ngx_http_core_run_phases;
            ngx_http_core_run_phases(r);
            return;
        }
    
        /*
        检查当前请求是否为subrequest子请求,如果是子请求,那么调用post_subrequest下的handler回调方法。subrequest的用法,可以看
        到post_subrequest正是此时被调用的。
         */  /* 如果当前请求是一个子请求,检查它是否有回调handler,有的话执行之 */  
        if (r != r->main && r->post_subrequest) {//如果当前请求属于某个原始请求的子请求
            rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc); //r变量是子请求(不是父请求)
        }
    
        if (rc == NGX_ERROR
            || rc == NGX_HTTP_REQUEST_TIME_OUT
            || rc == NGX_HTTP_CLIENT_CLOSED_REQUEST
            || c->error)
        {
            //直接调用ngx_http_terminate_request方法强制结束请求,同时,ngx_http_finalize_request方法结束。
            if (ngx_http_post_action(r) == NGX_OK) {
                return;
            }
    
            if (r->main->blocked) {
                r->write_event_handler = ngx_http_request_finalizer;
            }
    
            ngx_http_terminate_request(r, rc);
            return;
        }
    
        /*
        如果rc为NGX_HTTP_NO_CONTENT、NGX_HTTP_CREATED或者大于或等于NGX_HTTP_SPECIAL_RESPONSE,则表示请求的动作是上传文件,
        或者HTTP模块需要HTTP框架构造并发送响应码大于或等于300以上的特殊响应
         */
        if (rc >= NGX_HTTP_SPECIAL_RESPONSE
            || rc == NGX_HTTP_CREATED
            || rc == NGX_HTTP_NO_CONTENT)
        {
            if (rc == NGX_HTTP_CLOSE) {
                ngx_http_terminate_request(r, rc);
                return;
            }
    
            /*
                检查当前请求的main是否指向自己,如果是,这个请求就是来自客户端的原始请求(非子请求),这时检查读/写事件的timer_set标志位,
                如果timer_set为1,则表明事件在定时器申,需要调用ngx_del_timer方法把读/写事件从定时器中移除。
              */
            if (r == r->main) {
                if (c->read->timer_set) {
                    ngx_del_timer(c->read, NGX_FUNC_LINE);
                }
    
                if (c->write->timer_set) {
                    ngx_del_timer(c->write, NGX_FUNC_LINE);
                }
            }
    
            /* 设置读/写事件的回调方法为ngx_http_request_handler方法,这个方法,它会继续处理HTTP请求。 */
            c->read->handler = ngx_http_request_handler;
            c->write->handler = ngx_http_request_handler;
    
        /*
          调用ngx_http_special_response_handler方法,该方法负责根据rc参数构造完整的HTTP响应。为什么可以在这一步中构造这样的响应呢?
          这时rc要么是表示上传成功的201或者204,要么就是表示异步的300以上的响应码,对于这些情况,都是可以让HTTP框架独立构造响应包的。
          */
            ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc));
            return;
        }
    
        if (r != r->main) { //子请求
             /* 该子请求还有未处理完的数据或者子请求 */  
            if (r->buffered || r->postponed) { //检查out缓冲区内是否还有没发送完的响应
                 /* 添加一个该子请求的写事件,并设置合适的write event hander, 
                   以便下次写事件来的时候继续处理,这里实际上下次执行时会调用ngx_http_output_filter函数, 
                   最终还是会进入ngx_http_postpone_filter进行处理,在该函数中不一定会把数据发送出去,而是挂接到postpone链上,等高优先级的子请求先发送 */ 
                if (ngx_http_set_write_handler(r) != NGX_OK) { 
                    ngx_http_terminate_request(r, 0);
                }
    
                return;
            }
    
            /*
                由于当前请求是子请求,那么正常情况下需要跳到它的父请求上,激活父请求继续向下执行,所以这一步首先根据ngx_http_request_t结
            构体的parent成员找到父请求,再构造一个ngx_http_posted_request_t结构体把父请求放置其中,最后把该结构体添加到原始请求的
            posted_requests链表中,这样ngx_http_run_posted_requests方法就会调用父请求的write_event_handler方法了。
            */
            pr = r->parent;
    
            /*
              sub1_r和sub2_r都是同一个父请求,就是root_r请求,sub1_r和sub2_r就是ngx_http_postponed_request_s->request成员
              它们由ngx_http_postponed_request_s->next连接在一起,参考ngx_http_subrequest
    
                              -----root_r(主请求)     
                              |postponed
                              |                next
                -------------sub1_r(data1)--------------sub2_r(data1)
                |                                       |postponed                    
                |postponed                              |
                |                                     sub21_r-----sub22
                |
                |               next
              sub11_r(data11)-----------sub12_r(data12)
    
         */
            if (r == c->data) { 
            //这个优先级最高的子请求数据发送完毕了,则直接从pr->postponed中摘除,例如这次摘除的是sub11_r,则下个优先级最高发送客户端数据的是sub12_r
    
                r->main->count--; /* 在上面的rc = r->post_subrequest->handler()已经处理好了该子请求,则减1 */
                r->main->subrequests++;
    
                if (!r->logged) {
    
                    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
    
                    if (clcf->log_subrequest) {
                        ngx_http_log_request(r);
                    }
    
                    r->logged = 1;
    
                } else {
                    ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                                  "subrequest: "%V?%V" logged again",
                                  &r->uri, &r->args);
                }
    
                r->done = 1; /* 该子请求的handler已经处理完毕 */
                 /* 如果该子请求不是提前完成,则从父请求的postponed链表中删除 */  
                if (pr->postponed && pr->postponed->request == r) {
                    pr->postponed = pr->postponed->next;
                }
    
                /* 将发送权利移交给父请求,父请求下次执行的时候会发送它的postponed链表中可以 
                   发送的数据节点,或者将发送权利移交给它的下一个子请求 */ 
                c->data = pr;
            } else {
    
                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                               "http finalize non-active request: "%V?%V"",
                               &r->uri, &r->args);
                /* 到这里其实表明该子请求提前执行完成,而且它没有产生任何数据,则它下次再次获得 
                   执行机会时,将会执行ngx_http_request_finalzier函数,它实际上是执行 
                   ngx_http_finalize_request(r,0),也就是什么都不干,直到轮到它发送数据时, 
                   ngx_http_finalize_request 函数会将它从父请求的postponed链表中删除 */  
                r->write_event_handler = ngx_http_request_finalizer; //也就是优先级低的请求比优先级高的请求先得到后端返回的数据,
    
                if (r->waited) {
                    r->done = 1;
                }
            }
    
             /* 将父请求加入posted_request队尾,获得一次运行机会,这样pr就会加入到posted_requests,
             在ngx_http_run_posted_requests中就可以调用pr->ngx_http_run_posted_requests */  
            if (ngx_http_post_request(pr, NULL) != NGX_OK) {
                r->main->count++;
    ngx_http_terminate_request方法是提供给HTTP模块使用的结束请求方法,但它属于非正常结束的场景,可以理解为强制关闭请求。也就是说,
    当调用ngx_http_terminate_request方法结束请求时,它会直接找出该请求的main成员指向的原始请求,并直接将该原始请求的引用计数置为1,
    同时会调用ngx_http_close_request方法去关闭请求
    
    
    
    
                ngx_http_terminate_request(r, 0);
                return;
            }
    
            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                           "http wake parent request: "%V?%V"",
                           &pr->uri, &pr->args);
    
            return;
        }
    
        /* 这里是处理主请求结束的逻辑,如果主请求有未发送的数据或者未处理的子请求, 则给主请求添加写事件,并设置合适的write event hander, 
           以便下次写事件来的时候继续处理 */  
           
        //ngx_http_request_t->out中还有未发送的包体,
        //ngx_http_finalize_request->ngx_http_set_write_handler->ngx_http_writer通过这种方式把未发送完毕的响应报文发送出去
        if (r->buffered || c->buffered || r->postponed || r->blocked) { //例如还有未发送的数据,见ngx_http_copy_filter,则buffered不为0
    
            if (ngx_http_set_write_handler(r) != NGX_OK) { 
                ngx_http_terminate_request(r, 0);
            }
    
            return;
        }
    
        if (r != c->data) {
            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                          "http finalize non-active request: "%V?%V"",
                          &r->uri, &r->args);
            return;
        }
    
        r->done = 1;
        r->write_event_handler = ngx_http_request_empty_handler;
    
        if (!r->post_action) {
            r->request_complete = 1;
        }
    
        if (ngx_http_post_action(r) == NGX_OK) {
            return;
        }
    
        /* 到了这里真的要结束请求了。首先判断读/写事件的timer-set标志位,如果timer-set为1,则需要把相应的读/写事件从定时器中移除 */
    
        if (c->read->timer_set) {
            ngx_del_timer(c->read, NGX_FUNC_LINE);
        }
    
        if (c->write->timer_set) {
            c->write->delayed = 0;
            //这里的定时器一般在ngx_http_set_write_handler->ngx_add_timer中添加的
            ngx_del_timer(c->write, NGX_FUNC_LINE);
        }
    
        if (c->read->eof) {
            ngx_http_close_request(r, 0);
            return;
        }
    
        ngx_http_finalize_connection(r);
    }

    ngx_http_finalize_connection方法在结束请求时,解决了keepalive特性和子请求的问题  

    ngx_http_finalize_request -> ngx_http_finalize_connection

    static void
    ngx_http_finalize_connection(ngx_http_request_t *r) //ngx_http_finalize_request->ngx_http_finalize_connection
    {
        ngx_http_core_loc_conf_t  *clcf;
    
    #if (NGX_HTTP_V2)
        if (r->stream) {
            ngx_http_close_request(r, 0);
            return;
        }
    #endif
    
        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
    
        /*
        查看原始请求的引用计数.如果不等于1,则表示还有多个动作在操作着请求,接着继续检查discard_body标志位。如果
    discard_body为l,则表示正在丢弃包体,这时会再一次把请求的read_event_handler成员设为ngx_http_discarded_request_body_handler方法,
         */
        if (r->main->count != 1) {
    
            if (r->discard_body) {
                r->read_event_handler = ngx_http_discarded_request_body_handler;
                //将读事件添加到定时器中,其中超时时间是lingering_timeout配置项。
                ngx_add_timer(r->connection->read, clcf->lingering_timeout, NGX_FUNC_LINE);
    
                if (r->lingering_time == 0) {
                    r->lingering_time = ngx_time()
                                          + (time_t) (clcf->lingering_time / 1000);
                }
            }
    
            ngx_http_close_request(r, 0);
            return;
        }
    
        if (r->reading_body) {
            r->keepalive = 0; //使用延迟关闭连接功能,就不需要再判断keepalive功能关连接了
            r->lingering_close = 1;
        }
    
        /*
        如果引用计数为1,则说明这时要真的准备结束请求了。不过,还要检查请求的keepalive成员,如果keepalive为1,则说明这个请求需要释放,
    但TCP连接还是要复用的;如果keepalive为0就不需要考虑keepalive请求了,但还需要检测请求的lingering_close成员,如果lingering_close为1,
    则说明需要延迟关闭请求,这时也不能真的去结束请求,如果lingering_close为0,才真的结束请求。
         */
        if (!ngx_terminate
             && !ngx_exiting
             && r->keepalive
             && clcf->keepalive_timeout > 0) 
             //如果客户端请求携带的报文头中设置了长连接,并且我们的keepalive_timeout配置项大于0(默认75s),则不能关闭连接,只有等这个时间到后还没有数据到来,才关闭连接
        {
            ngx_http_set_keepalive(r); 
            return;
        }
    
        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方法延迟关闭请求。实际上,这个方法的意义就在于把一些必须做的事情做完
            (如接收用户端发来的字符流)再关闭连接。
            */
            ngx_http_set_lingering_close(r);
            return;
        }
    
        ngx_http_close_request(r, 0);
    }
    /*
    在引用计数清零时正式调用ngx_http_free_request方法和ngx_http_close_connection(ngx_close_connection) 方法来释放请求、关闭连接,见ngx_http_close_request,注意和ngx_http_finalize_connection的区别 */ static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_connection_t *c; //引用计数一般都作用于这个请求的原始请求上,因此,在结束请求时统一检查原始请求的引用计数就可以了 r = r->main; c = r->connection; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http request count:%d blk:%d", r->count, r->blocked); if (r->count == 0) { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "http request count is zero"); } r->count--; /* 在HTTP模块中每进行一类新的操作,包括为一个请求添加新的事件,或者把一些已绎由定时器、epoll中移除的事件重新加入其中,都需要把这个 请求的引用计数加1。这是因为需要让HTTP框架知道,HTTP模块对于该请求有独立的异步处理机制,将由该HTTP模块决定这个操作什么时候结束,防 止在这个操作还未结束时HTTP框架却把这个请求销毁了(如其他HTTP模块通过调用ngx_http_finalize_request方法要求HTTP框架结束请求),导致 请求出现不可知的严重错误。这就要求每个操作在“认为”自身的动作结束时,都得最终调用到ngx_http_close_request方法,该方法会自动检查引用 计数,当引用计数为0时才真正地销毁请求 由ngx_http_request_t结构体的main成员中取出对应的原始请求(当然,可能就是这个请求本身),再取出count引用计数并减l。 然后,检查count引用计数是否已经为0,以及blocked标志位是否为0。如果count已经为O,则证明请求没有其他动作要使用了,同时blocked标 志位也为0,表示没有HTTP模块还需要处理请求,所以此时请求可以真正释放;如果count引用计数大干0,或者blocked大于0,这样都不可以结 束请求,ngx_http_close_reques't方法直接结束。 */ if (r->count || r->blocked) { return; } //只有count为0才能继续后续的释放资源操作 #if (NGX_HTTP_SPDY) if (r->spdy_stream) { ngx_http_spdy_close_stream(r->spdy_stream, rc); return; } #endif #if (NGX_HTTP_V2) if (r->stream) { ngx_http_v2_close_stream(r->stream, rc); return; } #endif ngx_http_free_request(r, rc); ngx_http_close_connection(c); }
  • 相关阅读:
    React学习笔记
    士兵杀敌(一)
    网络爬虫Java实现抓取网页内容
    德莱联盟
    计算球体积
    在页面上 获取键盘事件
    一起玩转mysql
    腾讯云 做出的 不错的 动画 chrome小插件
    jquery MD5
    json to xml
  • 原文地址:https://www.cnblogs.com/codestack/p/13513781.html
Copyright © 2011-2022 走看看