亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

本篇文章首先簡單介紹了 TCP keepalive 的機(jī)制以及運(yùn)用場景。接著介紹了 Go 語言中如何開啟與設(shè)置 TCP keepalive。但是由于 Go 語言最上層的接口不夠靈活,從而引出在 Go 語言中如何使用系統(tǒng)調(diào)用設(shè)置 TCP 連接的文件描述符屬性。接著原作者就掉坑里了。。。最后介紹了在Go 1.11之后的版本如何使用新的接口設(shè)置 TCP 連接的文件描述符屬性。為了更適合中文閱讀,我對文章做了些增刪,并沒有逐字翻譯。原文地址:Notes on TCP keepalive in Go | TheNotExpert[1]

我有一個(gè)供客戶端連接的 TCP 服務(wù)端程序。它十分簡單。但問題是,所有的客戶端都使用手機(jī)移動網(wǎng)絡(luò)并且網(wǎng)絡(luò)總是不穩(wěn)定。經(jīng)常丟失連接卻沒有通過FIN或者RST包通知服務(wù)端。服務(wù)端保持著這個(gè)虛連接并且認(rèn)為這個(gè)客戶端仍然在線,而事實(shí)上卻不是。

我的首個(gè)解決方案是等待一小會;如果某個(gè)客戶端在給定的時(shí)間端沒有發(fā)送任何數(shù)據(jù),則在服務(wù)端關(guān)閉這個(gè)連接(值得一提,SetDeadline[2]方法十分好用,當(dāng)超時(shí)時(shí)它在conn.Read上返回i/o超時(shí)錯(cuò)誤)。但是以下情況需要考慮:我不能把超時(shí)設(shè)置得過小,因?yàn)榭蛻舳松蓴?shù)據(jù)的速度可能很慢,而且也不能把超時(shí)設(shè)置得過大,因?yàn)檫@會使我誤判客戶端的在線狀態(tài),而事實(shí)上我需要一定的精度。

我的想法是 ping 客戶端。但是我不想給客戶端發(fā)送它不需要的垃圾數(shù)據(jù)。而且,客戶端的代碼也不由我說了算,所以我也不確定如果我發(fā)送一些奇怪的數(shù)據(jù)給客戶端,客戶端會如何表現(xiàn)。

TCP-keepalive — 一個(gè)輕量級的 ping

TCP keepalive發(fā)送沒有(或者幾乎沒有)包體負(fù)載的 TCP 報(bào)文給對端,并且對端會回復(fù) keepalive ACK確認(rèn)包。它不是 TCP 標(biāo)準(zhǔn)的一部分(盡管在RFC1122[3]中有相關(guān)的描述),并且,它總是默認(rèn)被禁用。盡管如此,大部分現(xiàn)代的 TCP 協(xié)議棧都支持這個(gè)特性。

在它的大部分實(shí)現(xiàn)中,簡單來說,有三個(gè)主要參數(shù):

  • Idle time(空閑時(shí)間) - 接收一個(gè)包后,等待多長時(shí)間發(fā)出一個(gè) ping 包。
  • Retry interval(重試間隔時(shí)間) - 如果發(fā)送了一個(gè) ping,但是沒有收到對端回復(fù)的ACK,在重試間隔時(shí)間之后重新發(fā)送 ping。
  • Ping amount(重試次數(shù)) - 重試次數(shù)(沒有收到對端ACK)達(dá)到多少次后,我們認(rèn)為這個(gè)連接不存活了。

舉個(gè)例子,空閑時(shí)間是 30 秒,重試間隔時(shí)間是 5 秒,重試次數(shù)為 3。以下是它的工作方式:

服務(wù)端收到客戶端的一包應(yīng)用層數(shù)據(jù)。然后客戶端不再發(fā)送任何數(shù)據(jù)。服務(wù)端等待 30 秒。然后發(fā)送一個(gè) ping 給客戶端。如果服務(wù)端收到了ACK,則服務(wù)端等待另一個(gè) 30 秒,再次發(fā)送 ping;如果在這 30 秒內(nèi)服務(wù)端收到了數(shù)據(jù),則 30 秒的定時(shí)器被重置。

如果服務(wù)端沒有收到ACK,等待 5 秒后再次發(fā)送 ping。如果再過 5 秒還是沒有收到回復(fù)?發(fā)送最后一個(gè) ping 并等待最后一個(gè) 5 秒(是的,在最后一個(gè) ping 也需要等待重試間隔時(shí)間)。然后我們認(rèn)為這個(gè)連接超時(shí)了并且在服務(wù)端斷開它。

默認(rèn)值

據(jù)說 Window 系統(tǒng)在發(fā)送 keepalive ping 之前默認(rèn)等待 2 小時(shí)。linux 下獲取默認(rèn)值十分簡單,就像此處 3.1.1 節(jié)[4]描述的這樣。

# Idle time
cat /proc/sys/net/ipv4/tcp_keepalive_time
# Retry interval
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
# Ping amount
cat /proc/sys/net/ipv4/tcp_keepalive_probes

在 Go 語言中如何設(shè)置?

由于我最近使用 Go 語言比較多,我需要在 Go 語言中運(yùn)用 TCP keepalive。

討論開始之前需要說明,以下內(nèi)容適用于 Linux。我不是百分百確定它是否適用于 OSX,但我?guī)缀蹩梢钥隙ㄋ贿m用于 windows。

連接的特殊類型

首先,我注意到我在服務(wù)端程序中只使用了net.Conn[5]類型。但是它并不管用,它缺少我們需要的特定方法。我們需要TCPConn[6]類型。

這意味著,我們需要使用ListenTCP[7]和AcceptTCP[8]而不是Listen[9]和Accept[10](它們的調(diào)用方式有區(qū)別,ListenTCP使用結(jié)構(gòu)體而不是字符串來表示地址。我們調(diào)用方式大概會像這樣:ListenTCP("tcp", &net.TCPAddr{Port: myClientPort})。如果你不特別指定的話,IP 的默認(rèn)值為0.0.0.0)。之后它會返回我們需要的類型TCPConn。

Go 語言提供的方法

如果你翻看文檔可能會注意到這兩個(gè)相關(guān)的方法:SetKeepAlive[11]和SetKeepAlivePeriod[12]。func (c *TCPConn) SetKeepAlive(keepalive bool) error的調(diào)用方式十分簡單:傳入true從而打開 TCP keepalive 機(jī)制。

但是接下來的func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error就有些令人困惑了。我們用它究竟設(shè)置的是什么?答案可以在這篇文章[13](好文章,推薦閱讀)中找到:它同時(shí)設(shè)置了空閑時(shí)間重試間隔時(shí)間。而重試間隔次數(shù)則使用系統(tǒng)的默認(rèn)值。所以如果我設(shè)置5 * time.Second。那么它可能是等待 5 秒鐘,發(fā)送 ping 并等待另一個(gè) 5 秒。并且 8 次重試(取決于系統(tǒng)設(shè)置)。而我需要更大的靈活性,設(shè)置得更精準(zhǔn)。

進(jìn)入系統(tǒng)層面

可以通過直接操作 socket 參數(shù)來實(shí)現(xiàn)。我沒有關(guān)注里面太多的細(xì)節(jié),這純粹是我的個(gè)人解釋。以下是我們?nèi)绾卧O(shè)置空閑時(shí)間為 30 秒(我們可以通過SetKeepAlivePeriod設(shè)置,因?yàn)槠渌麉?shù)我們再另外設(shè)置),重試時(shí)間間隔設(shè)置為 5 秒,重試次數(shù)設(shè)置為 3。我偷了(啊呸,是參考了)上面所引用的文章中的一些代碼,多謝。

conn.SetKeepAlive(true)
conn.SetKeepAlivePeriod(time.Second * 30)
// Getting the file handle of the socket
sockFile, sockErr := conn.File()
if sockErr == nil {
 // got socket file handle. Getting descriptor.
 fd := int(sockFile.Fd())
 // Ping amount
 err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, 3)
 if err != nil {
 Warning("on setting keepalive probe count", err.Error())
 }
 // Retry interval
 err = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, 5)
 if err != nil {
 Warning("on setting keepalive retry interval", err.Error())
 }
 // don't forget to close the file. No worries, it will *not* cause the connection to close.
 sockFile.Close()
} else {
 Warning("on setting socket keepalive", sockErr.Error())
}

在這段代碼之后的某一行我會寫上dataLength, err := conn.Read(readBuf),這行代碼會阻塞直到收到數(shù)據(jù)或者發(fā)生錯(cuò)誤。如果是 keepalive 引起的錯(cuò)誤,err.Error()將會包含連接超時(shí)信息。

關(guān)于文件描述符的坑

上面的代碼只有在你不頻繁調(diào)用的前提下才運(yùn)行良好。在寫完這篇文章之后,我以困難模式學(xué)習(xí)到了一個(gè)關(guān)于它的小問題。。。

問題就隱藏在Fd[14]函數(shù)調(diào)用。我們來看它的實(shí)現(xiàn)。

func (f *File) Fd() uintptr {
 if f == nil {
 return ^(uintptr(0))
 }
 // If we put the file descriptor into nonblocking mode,
 // then set it to blocking mode before we return it,
 // because historically we have always returned a descriptor
 // opened in blocking mode. The File will continue to work,
 // but any blocking operation will tie up a thread.
 if f.nonblock {
 f.pfd.SetBlocking()
 }
 return uintptr(f.pfd.Sysfd)
}

如果文件描述符處于非阻塞模式,會將它修改為阻塞模式。根據(jù)stackoverflow 的這個(gè)回答[15],舉例來說,當(dāng) Go 增加一個(gè)阻塞的系統(tǒng)調(diào)用,運(yùn)行時(shí)調(diào)度器將該系統(tǒng)調(diào)用所屬協(xié)程所屬系統(tǒng)線程從調(diào)度池中移出。如果調(diào)度池中的系統(tǒng)線程數(shù)小于GOMAXPROCS,則會創(chuàng)建新的系統(tǒng)線程。鑒于我的每一個(gè)連接都使用一個(gè)獨(dú)立協(xié)程,你可以想象一下這個(gè)爆炸速度。將很快到達(dá) 10000 線程的限制然后 panic。

將它放入獨(dú)立協(xié)程并不好使。

譯者yoko注,個(gè)人理解此處可做兩層解釋,如果是像原作者所描述的,每個(gè)連接都獨(dú)占一個(gè)協(xié)程(直到連接關(guān)閉再退出協(xié)程),先使用系統(tǒng)調(diào)用設(shè)置文件描述符屬性,再收發(fā)數(shù)據(jù),那么系統(tǒng)線程會隨連接數(shù)線性增長。如果是在連接收發(fā)數(shù)據(jù)的協(xié)程之前,先弄一個(gè)協(xié)程處理完文件描述符屬性的設(shè)置,那么系統(tǒng)調(diào)用完成后臨時(shí)協(xié)程結(jié)束,線程還是會回收的。但也畢竟不是一種好的模式。

但是有一個(gè)方法是可行的。注意,前提是 Go 版本高于 1.11。看以下代碼。

//Sets additional keepalive parameters.
//Uses new interfaces introduced in Go1.11, which let us get connection's file descriptor,
//without blocking, and therefore without uncontrolled spawning of threads (not goroutines, actual threads).
func setKeepaliveParameters(conn devconn) {
 rawConn, err := conn.SyscallConn()
 if err != nil {
 Warning("on getting raw connection object for keepalive parameter setting", err.Error())
 }
 rawConn.Control(
 func(fdPtr uintptr) {
 // got socket file descriptor. Setting parameters.
 fd := int(fdPtr)
 //Number of probes.
 err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, 3)
 if err != nil {
 Warning("on setting keepalive probe count", err.Error())
 }
 //Wait time after an unsuccessful probe.
 err = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, 3)
 if err != nil {
 Warning("on setting keepalive retry interval", err.Error())
 }
 })
}
func deviceProcessor(conn devconn) {
 //............
 conn.SetKeepAlive(true)
 conn.SetKeepAlivePeriod(time.Second * 30)
 setKeepaliveParameters(conn)
 //............
 dataLen, err := conn.Read(readBuf)
 //............
}

最新版本的 Go 提供了一些新接口,net.TCPConn實(shí)現(xiàn)了SyscallConn[16],它使得你可以獲取RawConn[17]對象從而設(shè)置參數(shù)。你所需要做的就是定義一個(gè)函數(shù)(就像上面例子中的匿名函數(shù)),它接收一個(gè)指向文件描述符的參數(shù)。這是操作連接中的文件描述符而不造成阻塞調(diào)用的方法,可避免出現(xiàn)瘋狂創(chuàng)建線程的情況。

總結(jié)

網(wǎng)絡(luò)編程是復(fù)雜的。并且時(shí)常是系統(tǒng)相關(guān)的。這個(gè)解決方法只在 Linux 下有用,但是這是一個(gè)好的開始。在其他操作系統(tǒng)中有類似的參數(shù),它們只是調(diào)用方式不同。

感謝閱讀。再見。

本文原始地址:https://pengrl.com/p/62417/

文中鏈接

[1]

Notes on TCP keepalive in Go | TheNotExpert: https://thenotexpert.com/golang-tcp-keepalive/

[2]

SetDeadline: https://golang.org/pkg/net/#TCPConn.SetDeadline

[3]

RFC1122: https://tools.ietf.org/html/rfc1122#page-101

[4]

此處3.1.1節(jié): http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html

[5]

net.Conn: https://golang.org/pkg/net/#Conn

[6]

TCPConn: https://golang.org/pkg/net/#TCPConn

[7]

ListenTCP: https://golang.org/pkg/net/#ListenTCP

[8]

AcceptTCP: https://golang.org/pkg/net/#TCPListener.AcceptTCP

[9]

Listen: https://golang.org/pkg/net/#Listen

[10]

Accept: https://golang.org/pkg/net/#TCPListener.Accept

[11]

SetKeepAlive: https://golang.org/pkg/net/#TCPConn.SetKeepAlive

[12]

SetKeepAlivePeriod: https://golang.org/pkg/net/#TCPConn.SetKeepAlivePeriod

[13]

這篇文章: https://felixge.de/2014/08/26/tcp-keepalive-with-golang.html

[14]

Fd: https://golang.org/pkg/os/#File.Fd

[15]

stackoverflow的這個(gè)回答: https://stackoverflow.com/a/27603427/2052138

[16]

SyscallConn: https://golang.org/pkg/syscall/#Conn

[17]

RawConn: https://golang.org/pkg/syscall/#RawConn

分享到:
標(biāo)簽:語言
用戶無頭像

網(wǎng)友整理

注冊時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定