如何優雅的關閉關閉這個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); }
