閱讀882 返回首頁    go 阿裏雲 go 技術社區[雲棲]


[原創]nginx寫日誌時機與tcp write寫成功是否送達對端疑問解析

原創文章:來自nginx寫日誌時機與tcp write寫成功是否送達對端疑問解析

前些天和另外部門的同事在排查一個網絡問題, 問到nginx日誌中成功記錄了http 200響應碼能否證明響應數據就達到了對端? 這個問題涉及nginx在做server功能時寫日誌是在什麼時機? 是client端收到響應數據後才生成, 還是nginx丟出數據就生成了而不管是否client端收到數據?

做上層應用的人員一般對底層網絡部分研究的較少,另外針對應用層調用write寫TCP數據並返回寫入的字節數就認為成功到達了對端這個問題很多人存在誤解。涉及的nginx發出響應處理流程和linux係統TCP的處理過程,,簡要解釋如下:

nginx記錄訪問日誌的時機

通過ngx_http_output_filter()發送響應數據給client
ngx_http_write_filter()會調用c->send_chain()往客戶端發送數據,c->send_chain()的取值
在不同操作係統,編譯選項以及協議下(https下用的是
ngx_ssl_send_chain)會取不同的函數,典型的Linux操作係統下,它的取值為ngx_linux_sendfile_chain(),這個函數中使用rc = writev(c->fd, header.elts, header.nelts);發送數據;

響應的字節全部發送完成後調用 ngx_http_finalize_request(r, rc);

ngx_http_finalize_request()

-->ngx_http_terminate_request()強製結束請求

或者直接調用-->

-->ngx_http_close_request()

-->ngx_http_free_request()

-->ngx_http_log_request(r);//記錄日誌

再看一下TCP數據的發送流程

這裏定義為數據拷貝到協議棧緩存和緩存數據發送兩個階段。

應用層發送數據時調用TCP數據發送函數可以是write、send、sendmsg 這三個函數參數中攜帶需要發送的數據,最終在內核層麵都是通過調用__sock_sendmsg()實現,對TCP數據來說__sock_sendmsg()函數中會再調用tcp_sendmsg(); 而tcp_sendmsg()函數中根據現有的TCP緩存是否足夠會選擇將應用層的數據複製到sk_buff中,複製完成的sk_buff掛入到sk_write_queue 隊列尾部等待發送; 這個流程的代碼中用copied變量代表了真實從應用層已經拷貝到sk_buff中的數據量,下邊會走真正的TCP發送流程。但不管下邊的流程能否真正的發送出去數據,會返回這個copied到應用層代表寫入成功的數量; 而協議棧緩存數據的發送就由我們所熟知的TCP可靠傳輸機製去保證到達對端了。

協議棧緩存數據的發送通過以下幾個函數負責:

-->tcp_push()

-->__tcp_push_pending_frames()

-->tcp_write_xmit()

** 在tcp_write_xmit()中會逐步檢查本端的擁塞窗口(擁塞控製算法不斷的在調整)設置是否有配額cwnd_quota可以發送報文? 配額不足則跳出;

** 進行發送窗口snd_wnd 檢測, 若發送的MSS超過了發送窗口則跳出;

** 若開啟了Nagle算法不能立即發送此報文則跳出;

如果能通過以上等的限製則調用tcp_transmit_skb()真正的執行發送數據。

綜合所述TCP數據的發送其實是個異步的執行過程,應用層負責把數據寫入到TCP緩存中,而緩存中的數據需要靠TCP的可靠傳輸機製去保證發送到對端,由於TCP的可靠傳輸機製執行過程中會考慮擁塞窗口、對端接收窗口、MSS、nagle算法等因素,執行完成後返回的copied字節量並不一定就代表對端接收成功了。

最後更新:2017-08-14 06:32:18

  上一篇:go  點晴OA與釘釘哪個好用?
  下一篇:go  阿裏雲服務器選購要注意哪些問題?