我在 Linux 上運行我的服務器應用程式。我的服務器使用系結到地址的套接字*::<some_specific_port>
(其中*
表示通配符 IP 地址)。
我的程式可能被一些外部信號破壞(套接字將關閉close()
)或崩潰。
而且我想盡快重新啟動我的應用程式,而不關心 tcp 的可靠性(我會在更高級別上處理它)。當我加載我的服務器時,我使用完全相同的地址 ( *::<same_exact_port>
),但呼叫bind()
syscall 失敗,errno=EADDRINUSE
這意味著地址已在使用中。
我查了一下,發現socket處于TIME_WAIT狀態。在閱讀了一點之后,我發現了 Linux 和 tcp 中的重用地址問題。但正如我之前所說,就我而言,我并不真正關心可靠性,我關心的只是盡快重新啟動我的程式(始終使用通配符 ip 和相同的埠)。
我嘗試使用 SO_REUSEADDR 并將逗留時間設定為 0,但問題一直在發生。我已經看到 SO_REUSEPORT 選項似乎可以解決我的問題,但我更愿意盡可能避免使用它(出于安全目的)。
我閱讀了net.ipv4.tcp_tw_reuse
Linux 中的選項,但檔案非常模糊和不清楚。我注意到我的機器配置為net.ipv4.tcp_tw_reuse=0
,我想知道啟用此標志是否有幫助。
或者也許國旗不相關,我想念別的東西。
我看過這篇文章SO_REUSEADDR 和 SO_REUSEPORT 有何不同?,關于這個主題有一個很好的答案,但我仍然不明白當舊套接字處于 TIME_WAIT 狀態并且新套接字在 Linux 中使用 SO_REUSEADDR 設定時,我是否可以系結完全相同的地址(通配符和相同埠)。
uj5u.com熱心網友回復:
將逗留時間設定為零將導致您的套接字不等待發送未發送的資料(所有未發送的資料將立即丟棄),但是只有TIME_WAIT
在另一端已經關閉其寫入管道時才能確保避免狀態。
一個套接字可以看作是兩個管道。一個讀管道和一個寫管道。您的讀取管道連接到另一側的寫入管道,您的寫入管道連接到另一側的讀取管道。當你打開一個套接字時,兩個管道都打開了,當你關閉一個套接字時,兩個管道都關閉了。shutdown()
但是,您可以使用該呼叫關閉各個管道。
當您使用 shutdown 關閉您的寫入管道 (SHUT_WR
或SHUT_RDWR
) 時,您的套接字可能以 結束TIME_WAIT
,即使逗留時間為零。當你呼叫close()
一個套接字時,它會隱式關閉兩個管道,除非已經關閉,如果它確實關閉了寫管道,它必須等待,即使它從發送緩沖區中洗掉了任何掛起的資料。
如果對方close()
先呼叫或至少呼叫shutdown()
且SHUT_WR
僅在呼叫之后呼叫close()
,則套接字關閉可能只會延遲延遲時間,以確保發送未發送的資料或確認傳輸中的資料。在發送并確認所有資料之后或在達到延遲超時之后,無論首先發生什么,套接字都會立即關閉并且不會保持TIME_WAIT
狀態,因為是另一方首先發起斷開連接。
在某些系統上,將逗留時間設定為零會導致套接字通過重置 ( RST
) 而不是正常關閉 ( FIN, ACK
) 關閉,在這種情況下,所有未發送的資料都將被丟棄,并且套接字也不會進入TIME_WAIT
,因為重置后不需要這樣做,即使您先關閉套接字也不會。但是,如果零延遲時間是否觸發重置取決于系統,則您不能依賴它,因為沒有定義此行為的標準。如果您的套接字是阻塞的還是非阻塞的,以及shutdown()
之前是否被呼叫過,它也會有所不同close()
。
但是,如果您的應用程式在 TCP 傳輸程序中崩潰或被終止,則兩個管道都是打開的,系統必須代表您關閉套接字。在這種情況下,一些系統將簡單地忽略任何 linger 配置,而只是退回到標準行為,如果 linger 完全禁用,您也會得到這些行為。TIME_WAIT
這意味著即使系統上的逗留時間為零,您也可能最終會支持通過重置關閉套接字。同樣,這是特定于系統的,但過去已經在 macOS 系統上咬了我一口。
至于SO_REUSEADDR
,此設定不一定允許跨不同行程重用處于TIME_WAIT
狀態的套接字。如果行程 X 已經打開了 socketA 并且現在 socketA 處于TIME_WAIT
狀態,那么行程 X 可以肯定地將 socketB 系結到與 socketA 相同的地址和埠,當且僅當它使用SO_REUSEADDR
(在 Linux 的情況下,socket waiting 和新的需要那個標志,在 BSD 中只有新的需要它)。但是行程 Y 可能無法將套接字系結到與 socketA 相同的地址和埠,而 socketATIME_WAIT
出于安全原因仍處于狀態。
同樣,這是特定于系統的,Linux 的行為并不總是像 BSD 或 POSIX 所期望的那樣。它還可能取決于您使用的埠號。有時此限制僅適用于 1024 以下的埠(大多數測驗行為的人忘記同時測驗 1024 以上和以下的埠)。某些系統會另外限制對同一用戶的重用(IIRC Windows 有這種限制)。
那么你可以做些什么來解決這個問題呢?SO_REUSEPORT
是一個選項,因為它對在不同行程中使用完全相同的地址 埠組合沒有任何限制,因為它已明確引入 Linux 以允許不同行程重用埠以實作多個服務器行程之間的負載平衡。
另一種可能性是捕獲程式的任何終止(盡可能多地),然后以某種方式使另一端首先關閉套接字。只要對方發起關閉操作,你就永遠不會落入TIME_WAIT
. 當然,在信號處理程式中實作這一點很棘手,而且可能是不可能的,因為您的應用程式已經崩潰,因為您在信號處理程式中可以做的事情非常有限。通常,您可以通過在處理程式之外處理信號來解決此問題,但如果這是一個崩潰信號,則不清楚您仍然可以安全地執行哪些呼叫以及您不能執行哪些呼叫,即使您在與剛剛處理的執行緒不同的執行緒上處理信號也是如此墜毀。另請注意,您無法捕獲SIGKILL
,即使像這樣被殺死,系統也會干凈地關閉您的套接字。
一個很好的程式化解決方法:創建兩個行程。一個父行程,它完成所有的套接字管理,并產生一個子行程,然后處理實際的服務器實作。如果子行程被殺死,父行程仍然擁有所有套接字,仍然可以干凈地關閉它們,可以重新系結到相同的地址和埠SO_REUSEADDR
,甚至可以產生一個新的子行程,因此您的服務器繼續運行。
一些參考資料:
https://groups.google.com/g/comp.os.linux.development.system/c/sqxTvgccEzk
https://groups.google.com/g/alt.winsock.programming/c/md6bsoy08Fk
https://www.nybek.com/blog/2015/03/05/cross-platform-testing-of-so_linger/
https://www.nybek.com/blog/2015/04/29/so_linger-on-non-blocking-sockets/
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/462599.html