1. 程式人生 > >TCP連線被意外重置的原因

TCP連線被意外重置的原因

明顯伺服器端上層讀取過慢了, 視窗填滿了

今天在做伺服器壓力測試的時候,出現了很奇怪的情況,與伺服器建立連線會成功,但是很快會被重置(RESET)掉。花了半天時間,終於找到原因所在,我把過程和結果寫下來與大家分享。

    伺服器正常邏輯是:接受連線,等待使用者註冊報文,處理其他請求,如果連線一段時間沒有活動,則主動關閉連線。

    客戶端邏輯是:與伺服器建立連線後,馬上傳送註冊報文,然後每隔一段時間傳送一個請求。有上萬個客戶端同時連線一個伺服器,當連接出現錯誤時,馬上重新連線。

    出現錯誤時,客戶端會報告下面一連串錯誤:

recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer
recv: Connection reset by peer

    進一步的測試發現只有在客戶端數目超過一定數量才會出現這樣的情況,於是聯想到Linux程序開啟檔案描述符(Linux下套接字也是一種檔案描述符)的數量限制(前幾天剛剛增加了這個數量限制),但是到底有什麼聯絡不知道。

    開始以為伺服器程式邏輯主動關閉了連線,但是根據抓包的結果,伺服器根本就沒有傳送TCP FIN報文,下面是一個典型的連線建立與重置的過程:

14:01:03.567888 IP 192.168.6.45.36692 > 192.168.6.46.8080: S 1231228012:1231228012(0) win 5792 <mss 1460,sackOK,timestamp 174530727 168899918,nop,wscale 8>
14:01:03.567969 IP 192.168.6.46.8080 > 192.168.6.45.36692: S 909133089:909133089(0) ack 1231228013 win 5792 <mss 1460,sackOK,timestamp 168900338 174530727,nop,wscale 8>
14:01:03.567978 IP 192.168.6.45.36692 > 192.168.6.46.8080: . ack 1 win 23 <nop,nop,timestamp 174530727 168900338>
14:01:03.568022 IP 192.168.6.45.36692 > 192.168.6.46.8080: P 1:76(75) ack 1 win 23 <nop,nop,timestamp 174530727 168900338>
14:01:03.568110 IP 192.168.6.46.8080 > 192.168.6.45.36692: . ack 76 win 23 <nop,nop,timestamp 168900338 174530727>
14:01:03.568769 IP 192.168.6.46.8080 > 192.168.6.45.36692: R 1:1(0) ack 76 win 23 <nop,nop,timestamp 168900338 174530727>

    檢查伺服器的日誌,也沒有主動關閉連線的記錄,甚至並沒有接受到新的連線。這說明連線是由底層協議棧關閉的,但協議棧為什麼會主動關閉呢?

    用telnet連線伺服器,也不正常,但是是被正常關閉的(有正常的FIN序列),而不是重置。

    是否與偵聽套接字的就緒連線佇列長度有關?但是連線佇列滿時,協議棧不做任何操作,而是讓客戶端超市重發SYN報文,與出現的情況不一致。

    看來還是有開啟檔案描述符的數量限制有關,那麼不能再開啟檔案描述符時,會出現什麼情況呢?為什麼telnet連線會出現不同的情形呢?對這些問題的回答就要透過現象看本質了,我的分析如下:

    首先,連線確實是建立了,說明協議棧是接受了這個連線的,當然,應用程式肯定沒有接受,否則開啟檔案描述符數目超過上限了。另一方面,協議棧當然要關閉這個連線,但是沒有立即關閉,應該是在應用程式接受連線時(accept)關閉(尚未驗證),進一步跟蹤,accept會產生too many open files錯誤。這也說明套接字這個檔案描述符是在accept時開啟的,在協議棧中建立的連線並沒有對應的套接字描述符。

        telnet連線的不同之處在於沒有向伺服器傳送資料,此時採用正常方式關閉,這是協議的要求還是Linux實現的特例沒有考證過。

        還有另外一個結論是在accept之前收到的資料仍然會被接受並應答,並且連線上的資料是儲存在協議棧中的,這符合我先前的概念。