顯示具有 tcp 標籤的文章。 顯示所有文章
顯示具有 tcp 標籤的文章。 顯示所有文章

2012年8月7日 星期二

CLOSE_WAIT 與 TIME_WAIT

《TIME_WAIT and its design implications for protocols and scalable client server systems》 這篇真是不可思議的詳細易懂, 讀完後大概理解 TCP 結束時的前因後果了。

首先要理解的前提是, 無論 process 如何結束, 只要網路連線正常, TCP 會保證另一端得知連線結束。

接著問題要視主動 close 和被動 close 兩個方向來看。被動 close 比較單純, 會進入 CLOSE_WAIT, 程式要記得呼叫 close() 然後就沒事了。由 man page 得知, recv() 回傳 0 bytes 表示另一端結束連線。在 blocking IO 的情況下很單純, 不太會忘了 close。 non-blocking 的情況有可能寫出 bug, 而一直沒呼叫 close(), 這時用 netstat 可看到這類的連線狀態一直卡在 CLOSE_WAIT。

主動 close 的時候, 一切順利最後會進入 TIME_WAIT, 然後就是等一段時間才真的釋放使用的 port。對於同時需要主動連線出去 (做為別人的 client) 的 server 來說, 短時間內大量累加的 TIME_WAIT 有可能造成 port 不足而無法連線出去, 所以這篇的作者建議在設計 protocol 時, 盡量讓 client 主動結束連線。

綜合以上的訊息, 我理解的架構是: 盡可能讓 client 關掉連線, 再加上一個合理的長時間 timeout, 只有在 timeout 的情況下 server 才會主動關掉連線。發生 timeout 有兩種可能: client 其實「還在線上」, 只是很久沒傳資料; 或是 client 斷網後, 長於 timeout 的時間沒有連上網路。但不論何者, 為了減省 server 資源 (# of fd, memory, thread, etc), timeout 仍是必要的。

我之前在理解 TCP 斷線議題時, 一直卡在程式要怎麼知道對方斷線了? 搞清楚 kernel 實作 TCP 這點並做了些實驗後, 加上讀完這篇, 才恍然大悟。

TCP 與斷線

這件事困擾我一陣子, 一直搞不懂「網路斷線」是怎麼一回事。看了些資料做些實驗, 才發覺我搞錯斷線的意思。

TCP 是 kernel 實作的, 不管程式是自己掛掉還是被 SIGKILL 掛掉, 只要網路是通的, TCP 會如預期做結束連線的動作。即使中途 client 網路斷線, 接著 client 掛了, 待網路接通時, client 還是會送出結束連線的訊息給 server。偉哉! TCP!!

反過來說, 若網路真的不通, 在網路另一端的程式, 沒有方式可以得知這裡已經「斷線」, 挺多只能在接收或傳送資料時設個 timeout, 時間到沒回應就視對方為斷線。

總結來說, 只要另一端有辦法能連回網路, 無論發生什麼事, 都會通知對方結束網路連線, 這是由 kernel 保證的, 和 process 怎麼結束無關。但在網路不通的情況下, 雙方都無法得知對方是否真的結束了, 只能設個 timeout, 時間到就自己結束連線。

Btw, VM 真是測試網路斷線的好幫手, 方便在一台機器上做網路中斷的測試

2010年11月7日 星期日

Nagle's Algorithm 和 Delayed ACK 的問題

測 httplib2 POST 的時候發現 httplib2 實作造成的問題 (Issue 91), 就順著討論往下查原因, 還滿有趣的。

The trouble with the Nagle algorithm 簡單的解釋 Nagle's Algorithm 和 Delayed ACK 的目的和作法, 這篇文末兩段 "Delayed ACK" 和 "Nagle's Algorithm" 有更詳細的解釋。看懂後再回頭看 httplib2 Issue 91 的逐步說明, 終於明白為啥 Nagle's Algorithm 的作者說 write-write-read 會造成問題, 而 httplib2 剛好在 POST 的情況下就是 write-write-read (送 head, 送 body, 讀 response)。

在這裡整理一下讀到的重點:

TCP 的 ACK

用 TCP 傳資料時, 收到任何封包後都會回傳一個 ACK, 表示有收到該封包 (會有個流水號對應是收到那個封包)。

Delayed ACK

若收到資料的一端會馬上會回傳資料, 那就不用急著回傳 ACK, 可以等要回傳資料時, 再一起送回 ACK, 藉此少送一個封包。像 ssh 連線時, 每收到 client 端送來的按鍵, 都會將螢幕上的變化送回去。

但 TCP 不會知道 application layer 會不會立即回傳資料, 所以它只能先猜「會立即回傳」而先不回傳 ACK, 等個一陣子 (500ms?) 都沒有回傳資料的話, 再回傳 ACK。除此之外, 還有個例外規則, 一但收到第二個封包, 立即回傳這兩個封包的 ACK。

Nagle's Algorithm

收到送資料的請求時, 不會立即送出資料, 而是等下列兩個條件之一發生時, 才送出資料:
  • 送出的資料可以塞滿一個封包 (避免送出太多小封包, 浪費頻寬)。
  • 收到 ACK (表示之前的資料已成功送達, 此時不送也是讓頻寬空著)。

兩者的衝突

假設要送出的封包都不大。Nagle's Algorithm 會等到收到 ACK 後, 才會送出下一個封包。
  • 若操作是 write-read-write-read, 不會有問題, 因為對方的 Delayed ACK 猜中了, ACK 成功地搭回傳資料的便車送回來。
  • 但若是 write-write-read, 送完第一個封包後, 不會送出第二個封包, 因為沒有滿足 Nagle's Algorithm 的兩個條件。對方等個一段時間才會送出 ACK, 於是造成不必要的時間負擔。
如 Issue 91 第二則留言說的, 理想的解法是將 head 和 body 合在一個封包送出。關掉 client 端的 Nagle's Algorithm 可以避開這問題, 但卻不會避免送出一堆小封包。若 client 端不小心寫成送出一堆小封包, 會降低效率。

備註

如 Issue 91 作者所言, httplib2 只有在重用同一 connection 時才會有 write-write-read 卡住的問題。不知為何每次都用新的 connection 的話, 就沒有這個問題。得實際追踪送出封包和回 ACK 的時機, 才能明白為何用新的 connection 沒這問題。也許 Delayed ACK 或 Nagle's Algorithm 的運作方式不如上面所言那般單純。

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...