移動平臺下的Socket幾個問題

在頁遊時代,使用Flash ActionScript 3.0進行開發,as3提供比較簡單和健全的socket API。到了手遊時代,基於tcp的socket編程遇到了一些棘手的問題。通常情況下手遊都要支持至少二大主流平臺:Android、IOS,二者共存,暫時沒有跡象表現哪一方會沒落。

頁遊跑在瀏覽器裏,所有的連接成功、失敗等操作,都可以通過addEventListener進行監聽,很方便,一般也不存在頻繁掉線的情況。而手遊,因爲手機的便攜性決定了它的移動性,既然是可移動的那就會一定會面臨網絡不穩定的情況。

client與server通信如果使用TCP邏輯會比較簡單一些,但存在一些問題,這個問題在移動平臺下暴露的比較明顯。QQ客戶端使用的是UDP而非TCP,主要原因是因爲網絡的不穩定性。而TCP和UDP主要區別是什麼呢?其實就是長連接與短連接的區別

長連接是比較消耗資源的,但是通常情況下,一方斷了另一方會較爲及時的收到消息,業務邏輯上是比較簡單和及時的。

基於TCP的Socket網絡編程,如果想跨平臺,通常都使用C/C++進行封裝,這樣代碼層面至少是統一了。但移動設備上面臨的主要問題是頻繁的掉線,Android好一點,IOS其實是比較麻煩的。下面列一下在Android、IOS設備上HOME、電源鍵對網絡的影響:

平臺

Home鍵切後(網絡狀態)

電源鍵(網絡狀態)

Android

Y

Y

IOS

Y

N

其它的2G/3G/4G/Wifi之間的相互切換,也是比較麻煩,必須要重連了(因爲客戶端的IP已經發生變化了)。

電源鍵按下時,IOS就鎖屏了。Socket就斷掉了,但Server端並不會收到Client掉線。問題來了,不是說TCP是長連接嗎,我一端掉了那另一端應該收到斷開的消息啊,嗯,理論上是這樣子的,協議也是這麼規定的,但要先注意這樣一個問題:

TCP連接使用的是三次握手

TCP斷開使用的是四次握手

連接使用三次握手,這個不多說了,主要原因是爲了保證二端都能確認連接已經建立(SYN、ACK)。而斷開爲什麼是四次?因爲socket是雙工(雙向通信),相當於存在二條通訊的線路,一條用於接收,一條用於發送。一方主動關閉時(寫通道被關閉了,但此時讀通道還是正常的),它會發送FIN,另一端收到時會響應FIN+1(表示我收到你的關閉請求啦~),然後另一端處理完自己的邏輯後,告訴發起請求關閉的一方,我同意了你的關閉請求(不會再向你發送數據啦~),此時發起關閉的一方的讀通道纔是正常被關閉了。發起請求關閉的一方會在2MSL(報文最長生命週期的兩倍 Maximum Segment Lifetime)後釋放掉它所佔用的端口(連接記錄此時纔會被清除)。

所以,你會發現哇,原來關閉也是需要確認的。假設服務器突然斷電了,客戶端是不知道服務器端已經無法連接了的,還會認爲可以發送數據給服務器端。通常都是使用心跳包進行檢測來雙方的連接是否還存在。

我嘗試過在cocos2dx使用libuv來實現網絡通信,感覺異步寫起來確實過於繁瑣。libuv採用異步回調的寫法,所有的回調函數必須是static的。通常一款遊戲是有二個socket長連接的:遊戲主邏輯、聊天服務器,好在libuv支持回調參數裏“夾帶自定義參數”,倒也問題不大。不過我遇到一件奇葩的事情是,在三星GTI9000 Android 2.3.6系統上,將遊戲切入後臺,網絡狀態由2G變成wifi,不回調socket,調用發送之後也沒有觸發關閉回調方法,其它能借用到的Android設備都測試過,沒什麼問題了。wifi切到2G/3G,後臺切換至前臺後立馬觸發關閉的回調函數。

後端處理是這樣的,建立socket時會隨機生成一個密鑰串,當客戶端斷開連接時,拿這個密鑰串向服務器進行驗證,但是服務器驗證時有個特殊的判定,如果請求生成密鑰串的客戶端IP與重連時的客戶端IP不一致,則認爲是非法請求。也就是說2G切換至WIFI時,IP變了,服務器其實是直接將連接斷開了,但爲什麼沒觸發關閉的回調函數,這個或許是那個Android系統版本的bug吧

後來想的辦法有二個:

1、針對Android平臺,記錄連接時的網絡類型,然後切換至前臺時再獲取網絡類型,如果發現二次的網絡類型不一致就提示需要重新登錄遊戲了;

2、記錄建立連接時的IP地址,當切換至前臺再獲取IP,如果這二個IP不致,也認爲是需要重登錄遊戲了,因爲無論你拿什麼密鑰串都將無法再登錄遊戲,服務器認爲這個請求是非法的;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章