Go 語言使用 TCP keepalive
如果你寫過某些 TCP socket 代碼,你可能會疑問:如果網線被撥掉或者遠程主機崩潰了我的TCP連接會怎樣?
簡短的答案是:一點影響都沒有。這種情況下連接的結束遠程主機是不會發送FIN數據包的,并且本地系統不能檢測連接是否已中斷。所以需要作為程序員的你來解決這種情況。
GO語言為你提供了解決這個問題的幾種方法。首選的方法可能是 net.Conn 接口中的SetReadDeadline方法。假設你的連接在以一種特定的間隔來接收數據,你可以簡單地把讀取超時當作一個io.EOF錯誤并Close這個連接。很多現有的TCP協議都支持處理錯誤的這種方法,它們通過定義某種心跳機制或 service health 1,在端點間以特定間隔發送PING/PONG探測包來檢測雙方網絡問題。另外,這種心跳機制也可能有助于代理服務器查看網絡活動來決定連接的健康質量。
所以,如果你的協議支持心跳的話,或者你能夠為自己的協議加入心跳的話,這個方案應該是解決網絡掉線問題的首選。
但是,如果你對該協議沒有控制權并且它也不支持心跳你該怎么辦?
現在是時候該了解 TCP keepalive并在GO中使用它了。TCP keepalive定義于RFC 1122,但并不是TCP規范中的一部分。它可以在個別的連接中啟用,但默認必需是關閉的。啟用它會使網絡棧在空閑了特定時間后(不能低于2小時)探測連接的連接狀況。探測包不能包含數據2,并且一個探測包的回復的失敗不能將連接看作已中斷,因為探測包的傳輸是不可靠的。
GO 可以通過 net.TCPConn 的 SetKeepAlive 來啟用 TCP keepalive。在 OS X 和 Linux 系統上,當一個連接空間了2個小時時,會以75秒的間隔發送8個TCP keepalive探測包。換句話說, 在兩小時10分鐘后(7200+8*75)Read將會返回一個 io.EOF 錯誤.
對于你的應用,這個超時間隔可能太長了。在這種情況下你可以調用SetKeepAlivePeriod方法。但這個方法在不同的操作系統上會有不同的表現。在OSX上它會更改發送探測包前連接的空間時間。在Linux上它會更改連接的空間時間與探測包的發送間隔。所以以30秒的參數調用 SetKeepAlivePeriod在OSX系統上會導致共10分30秒(30+8*75)的超時時間,但在linux上卻是4分30秒(30+8*30).
我發現這種情況令人十分不滿意,所以我創建了一個包,名叫tcpkeepalive,用來提供給你更多的控制:
kaConn, _ := tcpkeepalive.EnableKeepAlive(conn) kaConn.SetKeepAliveIdle(30*time.Second) kaConn.SetKeepAliveCount(4) kaConn.SetKeepAliveInterval(5*time.Second)
目前,僅支持Linux和OS X,但是我很樂意將其他平臺上的pull requests合并。如果Go核心團隊的成員對此感興趣,我也愿意嘗試將這些新方法貢獻給Go本身。
請讓我知道你是否覺得這篇文章有價值,如果有任何疑問,請指出;并請指出任何錯誤,以便我可以進行更正。
附錄
1)早期通過一個較低,并且不真實的檢出率來調優一個心跳機制故障,是件棘手的事情。可以檢出 ? Accrual Failure Detector 來獲取一個統計模型,同樣也可以用 Damian Gryski 的 go-failure 擴展。可惜的是,我想不到有什么辦法可以在保活機制中使用它。
2)根據 RFC 1122 keepalive 分節,可能在零碎實現的兼容性中存在單個垃圾八進制數。然而,我不確定是不是被系統網絡堆棧過濾掉了,如果你知道,請在下面發評論留言。