Linux TCP RST情況

大雄45發表於2020-10-31
導讀 導致“Connection reset”的原因是伺服器端因為某種原因關閉了Connection,而客戶端依然在讀寫資料,此時伺服器會返回復位標誌“RST”,然後此時客戶端就會提示“java.net.SocketException: Connection reset”。可能有同學對復位標誌“RST”還不太瞭解,這裡簡單解釋一下:

Linux TCP RST情況Linux TCP RST情況
TCP建立連線時需要三次握手,在釋放連線需要四次揮手;例如三次握手的過程如下:

第一次握手:客戶端傳送syn包(syn=j)到伺服器,並進入SYN_SENT狀態,等待伺服器確認;

第二次握手:伺服器收到syn包,並會確認客戶的SYN(ack=j+1),同時自己也傳送一個SYN包(syn=k),即SYN+ACK包,此時伺服器進入SYN_RECV狀態;

第三次握手:客戶端收到伺服器的SYN+ACK包,向伺服器傳送確認包ACK(ack=k+1),此包傳送完畢,客戶端和伺服器進入ESTABLISHED(TCP連線成功)狀態,完成三次握手。

可以看到握手時會在客戶端和伺服器之間傳遞一些TCP頭資訊,比如ACK標誌、SYN標誌以及揮手時的FIN標誌等。

除了以上這些常見的標誌頭資訊,還有另外一些標誌頭資訊,比如推標誌PSH、復位標誌RST等。其中復位標誌RST的作用就是“復位相應的TCP連線”。

TCP連線和釋放時還有許多細節,比如半連線狀態、半關閉狀態等。詳情請參考這方面的鉅著《TCP/IP詳解》和《UNIX網路程式設計》。

前面說到出現“Connection reset”的原因是伺服器關閉了Connection[呼叫了Socket.close()方法]。大家可能有疑問了:伺服器關閉了Connection為什麼會返回“RST”而不是返回“FIN”標誌。原因在於Socket.close()方法的語義和TCP的“FIN”標誌語義不一樣:傳送TCP的“FIN”標誌表示我不再傳送資料了,而Socket.close()表示我不在傳送也不接受資料了。問題就出在“我不接受資料” 上,如果此時客戶端還往伺服器傳送資料,伺服器核心接收到資料,但是發現此時Socket已經close了,則會返回“RST”標誌給客戶端。當然,此時客戶端就會提示:“Connection reset”。詳細說明可以參考oracle的有關文件:http://docs.oracle.com/javase/1.5.0/docs/guide/net/articles/connection_release.html。

另一個可能導致的“Connection reset”的原因是伺服器設定了Socket.setLinger (true, 0)。但我檢查過線上的tomcat配置,是沒有使用該設定的,而且線上的伺服器都使用了nginx進行反向代理,所以並不是該原因導致的。關於該原因上面的oracle文件也談到了並給出瞭解釋。

此外囉嗦一下,另外還有一種比較常見的錯誤“Connection reset by peer”,該錯誤和“Connection reset”是有區別的:

伺服器返回了“RST”時,如果此時客戶端正在從Socket套接字的輸出流中讀資料則會提示Connection reset”;

伺服器返回了“RST”時,如果此時客戶端正在往Socket套接字的輸入流中寫資料則會提示“Connection reset by peer”。

“Connection reset by peer”如下圖所示:

前面談到了導致“Connection reset”的原因,而具體的解決方案有如下幾種:

出錯了重試;

客戶端和伺服器統一使用TCP長連線;

客戶端和伺服器統一使用TCP短連線。

首先是出錯了重試:這種方案可以簡單防止“Connection reset”錯誤,然後如果服務不是“冪等”的則不能使用該方法;比如提交訂單操作就不是冪等的,如果使用重試則可能造成重複提單。

然後是客戶端和伺服器統一使用TCP長連線:客戶端使用TCP長連線很容易配置(直接設定HttpClient就好),而伺服器配置長連線就比較麻煩了,就拿tomcat來說,需要設定tomcat的maxKeepAliveRequests、connectionTimeout等引數。另外如果使用了nginx進行反向代理或負載均衡,此時也需要配置nginx以支援長連線(nginx預設是對客戶端使用長連線,對伺服器使用短連線)。

使用長連線可以避免每次建立TCP連線的三次握手而節約一定的時間,但是我這邊由於是內網,客戶端和伺服器的3次握手很快,大約只需1ms。ping一下大約0.93ms(一次往返);三次握手也是一次往返(第三次握手不用返回)。根據80/20原理,1ms可以忽略不計;又考慮到長連線的擴充套件性不如短連線好、修改nginx和tomcat的配置代價很大(所有後臺服務都需要修改);所以這裡並沒有使用長連線。

正常情況tcp四層握手關閉連線,rst基本都是異常情況,整理如下:

0.使用 ping 可以看到丟包情況

1. **
2. 對方埠未開啟,發生在連線建立

如果對方sync_backlog滿了的話,sync簡單被丟棄,表現為超時,而不會rst[/yiji]

3. close Socket 時recv buffer 不為空

例如,客戶端發了兩個請求,伺服器只從buffer 讀取第一個請求處理完就關閉連線,tcp層認為資料沒有正確提交到應用,使用rst關閉連線。

4. 移動鏈路

行動網路下,國內是有5分鐘後就回收信令,也就是IM產品,如果心跳>5分鐘後伺服器再給客戶端發訊息,就會收到rst。也要查行動網路下IM 保持<5min 心跳。

5. 負載等裝置

負載裝置需要維護連線轉發策略,長時間無流量,連線也會被清除,而且很多都不告訴兩層機器,新的包過來時才通告rst。

Apple push 服務也有這個問題,而且是不可預期的偶發性連線被rst;rst 前第一個訊息write 是成功的,而第二條寫才會告訴你連線被重置,

曾經被它折騰沒轍,因此開啟每2秒一次tcp keepalive,固定5分鐘tcp連線回收,而且發現連線出錯時,重發之前10s內訊息。

6. SO_LINGER 應用強制使用rst 關閉

該選項會直接丟棄未傳送完畢的send buffer,可能造成業務錯誤,慎用; 當然內網服務間http client 在收到應該時主動關閉,使用改選項,會節省資源。

好像曾經測試過haproxy 某種配置下,會使用rst關閉連線,少了網路互動而且沒有TIME_WAIT 問題

7. 超過超時重傳次數、網路暫時不可達
8. TIME_WAIT 狀態

tw_recycle = 1 時,sync timestamps 比上次小時,會被rst[/yiji]

9. 設定 connect_timeout

應用設定了連線超時,sync 未完成時超時了,會傳送rst終止連線。[/yiji]

10. 非正常包

連線已經關閉,seq 不正確等

11. keepalive 超時

公網服務tcp keepalive 最好別開啟;行動網路下會增加網路負擔,切容易掉線;非行動網路核心ISP裝置也不一定都支援keepalive,曾經也發現過廣州那邊有個核心節點就不支援。

12. 資料錯誤,不是按照既定序列號傳送資料
13.在一個已關閉的socket上接收資料
14.伺服器關閉或異常終止了連線由於網路問題

客戶端沒有收到伺服器的關閉請求,這稱為TCP半開啟連線。就算重啟伺服器,也沒有連線資訊。如果客戶端向提其寫入資料,對方就會回應一個RST報文段。

原文來自:  https://www.linuxprobe.com/tcp-rst.html


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2731408/,如需轉載,請註明出處,否則將追究法律責任。

相關文章