作者:謝代斌
研究測試TCP斷開和異常的各種情況,以便于分析網絡應用(比如tconnd)斷網的原因和場景,幫組分析和定位連接配接異常掉線的問題,并提供給TCP相關的開發測試人員作為參考。
各個遊戲接入都存在一定的掉線問題,而且有的遊戲項目的掉線比例還比較高,現在互娛自研遊戲的網絡接入基本上都用的是tconnd和ProtocalHandler元件(該元件請參考附件的《TSF4G_ProtocalHandler開發指導手冊》),是以參與其掉線原因分析和研究。
在參與A項目的掉線問題研究分析過程中,tconnd增加了玩家每個連接配接的流水日志和ProtocalHandler增加了每個連接配接的Qos上報日志,通過這些日志記錄了每一次連接配接的斷開原因和相關統計資料,其中包括了連接配接異常斷開時TCP的底層錯誤碼。
通過對tconnd的流水日志和ProtocalHandler的Qos日志進行統計分析,發現連接配接異常斷開時TCP的錯誤碼大部分是“<code>104: Connection reset by peer</code>”(Linux下)或“<code>10054: An existing connection was forcibly closed by the remote host</code>”(Windows下),單純從錯誤碼本來來說,大家都明白是“網絡被對端重置了”,但究竟什麼情況下會導緻這種情況呢?是以就對TCP的各種關閉情況做了進一步的測試研究。
伺服器程式在接受用戶端的TCP連接配接後Sleep幾秒鐘,用戶端程式在TCP連接配接後立即發送很多消息給對端後做相應動作(退出或等待),伺服器程式Sleep完後開始Recv消息。
注意:伺服器程式測試了Linux和Windows版本,但用戶端隻測試了Windows版本,如果是Linux用戶端則有些Case的結果會不一樣。
用戶端程式正常運作的情況下,拔掉網線,殺掉用戶端程式目的:模拟用戶端當機、系統突然重新開機、網線松動或網絡不通等情況。結論:這種情況下伺服器程式沒有檢測到任何異常,并最後等待“逾時”才斷開TCP連接配接。
用戶端程式發送很多資料包後正常關閉Socket并exit程序(或不退出程序)
目的:模拟用戶端發送完消息後正常退出的情況。
結論:這種情況下伺服器程式能夠成功接收完所有消息,并最後收到“對端關閉”(Recv傳回零)消息。
用戶端程式發送很多資料包後不關閉Socket直接exit程序目的:模拟用戶端程式退出而忘記關閉Socket的情況(比如通過Windows視窗的關閉圖示退出程序,而沒有捕獲相應關閉事件做正常退出處理等)。 結論:這種情況下伺服器程式能夠收到部分TCP消息,然後收到“104: Connection reset by peer”(Linux下)或“10054: An existing connection was forcibly closed by the remote host”(Windows下)錯誤。
用戶端程式發送很多資料包的過程中直接Kill程序
目的:模拟用戶端程式崩潰或非正常方式結束程序(比如Linux下"kill -9"或Windows的任務管理器殺死程序)的情況。
結論:這種情況下伺服器程式很快收到“<code>104: Connection reset by peer</code>”(Linux下)或“<code>10054: An existing connection was forcibly closed by the remote host</code>”(Windows下)錯誤。
伺服器程式在接受用戶端的TCP連接配接後Sleep幾秒鐘,用戶端程式在TCP連接配接後立即發送很多消息給對端後做相應動作(退出或等待),伺服器程式Sleep完後開始Recv和Send消息。
注意:伺服器程式測試了Linux和Windows版本,但用戶端隻測試了Windows版本,如果是Linux用戶端則有些Case的結果可能會不一樣。
用戶端程式發送很多資料包後正常關閉Socket并exit程序(或不退出程序)目的:模拟用戶端正常關閉Socket後,伺服器端在檢查到TCP對端關閉前向用戶端發送消息的情況。結論:這種情況下伺服器程式接收和發送部分TCP消息後,在Send消息時産生“32: Broken pipe”(Linux下)或“10053: An established connection was aborted by the software in your host machine”(Windows下)錯誤。
用戶端程式發送很多資料包後不關閉Socket直接exit或Kill程序
目的:模拟用戶端程式退出而忘記關閉Socket、或用戶端程式崩潰或非正常方式結束程序的情況。
結論:這種情況下伺服器程式在Recv或Send消息時産生“<code>104: Connection reset by peer</code>”(Linux下)或“<code>10054: An existing connection was forcibly closed by the remote host</code>”(Windows下)錯誤。
TCP發現網絡異常(特别是Linux下的104錯誤或Windows下10054錯誤)的情況很多,比如網絡本身的問題、中間路由器問題、網卡驅動器問題等不可抗拒因素,但下面是應用程式本身可能會導緻的問題,也是我們需要進一步研究和解決的情況,特别是程式崩潰導緻問題:
當TCP連接配接的程序在忘記關閉Socket而退出、程式崩潰、或非正常方式結束程序的情況下
(Windows用戶端),會導緻TCP連接配接的對端程序産生“<code>104: Connection reset by peer</code>”(Linux下)或“<code>10054: An existing connection was forcibly closed by the remote hos</code>t”(Windows下)錯誤。
當TCP連接配接的程序機器發生當機、系統突然重新開機、網線松動或網絡不通等情況下
-(Windows用戶端),連接配接的對端程序可能檢測不到任何異常,并最後等待“逾時”才斷開TCP連接配接。
當TCP連接配接的程序正常關閉Socket時,對端程序在檢查到TCP關閉事件之前仍然向TCP發送消息
(Windows用戶端),則在Send消息時會産生“<code>32: Broken pipe</code>”(Linux下)或“<code>10053: An established connection was aborted by the software in your host machine</code>”(Windows下)錯誤。3.2 效果針對A項目的掉線問題,通過問卷調查和聯系個别玩家等方法,發現掉線的情況很大部分是用戶端程式直接退出了,是以推動項目組實作了用戶端的Qos上報功能,最後通過用戶端的Qos上報的統計資料得出用戶端程式的崩潰比例比較高,占了總掉線的很大比率,當然其它情況也存在,但比例相對比較小。是以,A項目首先應該解決用戶端程式的崩潰問題,如果該問題解決了,也就解決大部分掉線問題。二. TCP異常關閉的進一步研究測試1. 背景B項目遊戲在跨服跳轉時的掉線比例比較高,經過分析ProtocalHandler和tconnd的日志,發現掉線出現的情況是:tconnd發送了跨服跳轉消息後立即關閉了Socket,用戶端程序在接收到跨服跳轉消息之前發送消息後收到Windows 10054錯誤,然後做斷線重連失敗。B項目實作跨服跳轉的流程是GameSvr給用戶端程式下發的跨服跳轉指令的同時攜帶了Stop請求,也就是說tconnd在向用戶端轉發跨服跳轉消息後立即就會關閉目前的Socket連接配接,而且B項目的用戶端程式會定期不斷地向伺服器上報消息。這又怎麼會導緻用戶端程式收到10054錯誤而呢?鑒于此,對TCP的連接配接做進一步的場景測試分析。2. TCP異常進一步測試研究2.1 測試方法用戶端和伺服器端程式建立TCP連接配接,伺服器程式在TCP緩沖區中有消息或沒有消息的情況下關閉Socket,用戶端在對端Socket已經關閉的情況下繼續Send和Recv消息。注意:伺服器端隻測試了Linux版本,但用戶端測試了Windows和Linux兩個版本。2.2 測試結果
伺服器端已經close了Socket,用戶端再發送資料
目的:測試在TCP對端程序已經關閉Socket時,本端程序還未檢測到連接配接關閉的情況下繼續向對端發送消息。
結論:第一包可以發送成功,但第二包發送失敗,錯誤碼為“<code>10053: An established connection was aborted by the software in your host machine</code>”(Windows下)或“<code>32: Broken pipe,同時收到SIGPIPE信号</code>”(Linux下)錯誤。
伺服器端發送資料到TCP後close了Socket,用戶端再發送一包資料,然後接收消息目的:測試在TCP對端程序發送資料後關閉Socket,本端程序還未檢測到連接配接關閉的情況下發送一包消息,接着接收消息。結論:用戶端能夠成功發送第一包資料(這會導緻伺服器端發送一個RST包 <已抓包驗證>),用戶端再去Recv時,對于Windows和Linux程式有如下不同的表現:
Windows用戶端程式:Recv失敗,錯誤碼為“<code>10053: An established connection was aborted by the software in your host machine</code>”。Linux用戶端程式:能正常接收完所有消息包,最後收到正常的對端關閉消息(這一點與Window下不一樣,RST包沒有被提前接收到)。
伺服器端在TCP的接收緩沖區中還有未接收資料的情況下close了Socket,用戶端再收包
目的:測試在TCP的接收緩沖區中還有未接收資料的情況下關閉Socket時,對端程序是否正常。
結論:這種情況伺服器端就會向對端發送RST包,而不是正常的FIN包(已經抓包證明),這就會導緻用戶端提前(RST包比正常資料包先被收到)收到“<code>10054: An existing connection was forcibly closed by the remote host</code>”(Windows下)或“<code>104: Connection reset by peer</code>”(Linux下)錯誤。
TCP應用程式某些看是正常的行為下也可能會導緻對端接收到異常,比如當TCP接收緩沖區中還有未收資料的情況下關閉Socket,則會導緻對端接收到異常關閉而不是正常關閉;反過來說,當TCP檢測到異常關閉時并不一定表示業務上出問題了,因為很可能就是業務正常結束了。下面是本次測試的主要結論:
當TCP連接配接的對端程序已經關閉了Socket的情況下,本端程序再發送資料時,第一包可以發送成功(但會導緻對端發送一個RST包過來):之後如果再繼續發送資料會失敗,錯誤碼為“<code>10053: An established connection was aborted by the software in your host machine</code>”(Windows下)或“32: Broken pipe,同時收到SIGPIPE信号”(Linux下)錯誤;之後如果接收資料,則Windows下會報10053的錯誤,而Linux下則收到正常關閉消息。
TCP連接配接的本端接收緩沖區中還有未接收資料的情況下close了Socket,則本端TCP會向對端發送RST包,而不是正常的FIN包,這就會導緻對端程序提前(RST包比正常資料包先被收到)收到“<code>10054: An existing connection was forcibly closed by the remote host</code>”(Windows下)或“<code>104: Connection reset by peer</code>”(Linux下)錯誤。
B項目跨服跳轉的掉線問題有相當一部分的種情況是tconnd向用戶端轉發跨服跳轉消息後立即關閉Socket連接配接,而此時剛好用戶端向tconnd發送了資料包:
第一種情況:tconnd在關閉Socket的時刻其TCP的接收緩沖區中有未收的消息,這就使得tconnd程序的TCP向用戶端發送的是RST包而不是正常結束的FIN包,是以用戶端程式就會提前收到RST包(RST包會比正常資料提前收到),而不會先收完跨服跳轉消息後再接收到正常結束消息,這就導緻用戶端收到網絡異常斷線而做重連,但之前的連接配接是tconnd主動關閉的,是以不可能重連成功,進而導緻掉線。
第二種情況:tconnd已經關閉了Socket後,用戶端在接收到跳轉消息和檢測到TCP關閉之前向tconnd發送了消息,這就會導緻用戶端程式收到異常斷線而做重連并失敗。
最後,與B項目項目組一起讨論,改進了大部分跨服跳轉的業務流程後,掉線比例j減少了很多,當然還是存在一定比例的掉線,但這應該就是其它一些原因了(網絡異常問題原因很多,國内目前的網絡環境下,掉線問題是不可能完全避免的)。
通常情況下,向TCP的Socket發送完資料後關閉Socket,大家認為這樣很正常的方式肯定沒有問題,對端應該正确收完資料後收到TCP的關閉消息,但實際上在某些情況下并非如此:當TCP本端的接收緩沖區中有未收的資料時關閉Socket,這會導緻對端收到RST的異常關閉消息;當對端在本端已經關閉Socket的情況下再次發送消息時,也會導緻對端收到異常關閉消息;還有為了避免TIME_WAIT而設定了SO_LINGER選項的話,也會導緻連接配接提前夭折使對端收到RST異常關閉消息。
有些時候業務流程對是否引起掉線也很重要(特别是連接配接關閉流程),比如前面的B項目的跨服跳轉掉線問題很大部分就是因為GameSvr請求關閉連接配接導緻的。建議各個遊戲項目的關閉流程(包括跨服跳轉的原連接配接的關閉)最好都由用戶端發起關閉,這樣就一定程度上避免上述問題的發生(因為客服端發起關閉的時候,一般業務流程都走完了,伺服器端也不會再向用戶端發送消息了)。
程式收到網絡異常的情況很多(最多的就是Linux下的104錯誤和Windos下的10054/10053錯誤):有網絡本身的問題、也有應用使用不當的問題;有營運商之間的跨網絡問題、網絡中間路由器問題、玩家機器硬體(比如網卡及其驅動)問題和作業系統、防毒軟體、防火牆等軟體問題,還有玩家的上網裝置和路由器等中間裝置問題等,但用戶端程式崩潰問題有可能會占掉線的很高比例,這也是值得我們注意和改進的地方。還有種情況值得我們注意,有些TP-LINK的路由器,當UDP包大小超過其MTU值時會導緻使用者機器的網絡斷開,進而引起掉線(這個問題在某些項目的個别玩家中已經出現過)。
網絡異常關閉引起掉線是目前遊戲中普遍存在的問題,差別隻在于掉線的比例多少,特别是國内各營運商之間跨網絡通路更是不太順暢,要将其完全消除是不可能的,但我們的目标是将其控制在較小的可接受範圍内。