在移动网络上创建更稳定的连接
轉自:http://blog.codingnow.com/2014/02/connection_reuse.html
我們的手機游戲發布有一段時間了。立項之前我寫的一篇 blog?, 在移動設備上開發游戲需要克服的兩大技術難點: 移動網絡的不穩定性以及手機硬件資源的約束。由于開發時間所限,第一點我們并沒有專門去做。
我一直不想動手去做一個臨時方案解決 TCP 斷線重連問題,因為實現一個 TCP over TCP 是沒有太大意義的。移動網絡發展迅速的今天,整個行業都在努力提高移動網絡的穩定性,所以費力做這個事情很可能在兩年之后就變得完全沒有必要。
比如,iOS 7.0 發布?后,讓?MultiPath TCP?技術為更多人所知。從許多中文資料對其的解讀,主要集中在 MPTCP 提供了更大的帶寬上;甚至一些網絡噴子借機來噴國內的 3G 收費高的問題,認為同時利用 3G 網絡和 wifi 下載沒有意義。但我認為其對于移動網絡的真正意義在于提供一個更加穩定的連接。
顧名思義,MPTCP 允許在同一 TCP 連接的通訊兩端建立多條通訊路徑,如這篇文章?所言:Just like IP can hide routing changes, MPTCP can hide the details of which paths it is using at any given time.
這兩天,我們在自己的服務器上安裝了支持 MPTCP 的新內核做了測試。發現:如有可能,設備會為新的 IP 地址建立新的通訊路徑。如果連接兩端各有兩個 IP ,那么在初始的 TCP 連接建立后,通過協商,最終會建立 4 條 TCP 連接出來,交叉連接了所有的 IP 。任何一條通路有效都不影響通訊。btw, 如果你的機房有網通,電信兩個 IP 的話,如果客戶端設備支持 MPTCP ,那么會自動同時使用兩個通路同時維持一個邏輯上的連接。這對國內的網絡環境非常有利,不需要使用 bgp 機房,也不需要在多線機房配置復雜的 DNS 了。
當你的手機從 3G 網絡切換到新的 wifi 熱點時,設備會自動利用新的 wifi 網絡做數據傳輸;離開 wifi 熱點后,又能無縫切換回 3G ;再次進入新的 wifi 熱點范圍,還可以重新利用新的 wifi 網絡。這樣,移動設備可以穿梭于多個網絡之間而永不斷開連接。
可惜的是,Apple 目前并沒有完全開放 MPTCP 給應用層使用。經我的測試,只有 Siri 的連接才會發送 MPTCP 握手協商。這篇 blog 也證實了這一點?。
ps. 經過這兩天的測試,還發現 MPTCP 似乎只能利用第一次連接的通路做控制信息交換。當第一次連接的 IP 實效后,不能把后來的通路提升為主控連接。所以 MPTCP 看起來不能在只有一個網絡設備上正常工作。(我原先預期它可以在同一個設備上切換 IP 還可以正常建立新的子流,看來是搞錯了)
借著閱讀 MPTCP 的協議文檔,我也想了許多。我覺得在現階段在應用層上實現一個更穩定的 TCP 連接也是可行的。但協議設計要考慮的很多,下面記錄一下我的設計方案:
我希望針對游戲服務器的特性,實現一個不對稱的連接協議。即,只能由客戶端發起連接,而發起連接的一方無法主動斷開連接。服務器只接受連接,有權利斷開連接。
這個協議基于已有的 TCP 協議,通訊是基于帶長度信息的包構成。客戶端到服務器的前兩個包為握手包,服務器只用回應第一個握手包,客戶端發送的第二個握手包用于校驗,當服務器不認可握手過程,可直接斷開連接。
連接建立過程如下:
.1. 客戶端向服務器發起一個 TCP 連接,并發送第一個握手信號:包含一個 0 和按 Diffie-Hellman 密鑰交換算法產生一串隨機量 A 。
.2. 服務器收到第一個握手信號后,檢查第一個字段,若不為 0 則進入連接修復階段 2.2,否則繼續創建連接過程 2.1。
.2.1 此時服務器生成另一個隨機串 B ,并通過 DH 密鑰交換算法得到了一個 secret 。此時回應 DH 算法需要的 B ,以及一個新的隨機串 E (用于校驗)。
.2.2 當第一個字段不為 0 則認為是需要修復一個已有連接,這個數字表示在舊連接上客戶端已收到過服務器發過來的數據包數量。此時,第二個字段應理解為舊連接上已收到數據包的指紋。服務器根據包數量和指紋可以核對所有保持的有效連接,如果不能找到匹配的連接(指紋相同),就斷開客戶端。否則回應客戶端在舊連接上一共收到客戶端發送的數據包數量,以及一個新的隨機串 E ,用于確認客戶端是否知道舊連接的 secret 。(這個校驗是有必要的,否則會有人監聽到鏈接重建過程,而重復發送這個握手包來踢掉合法用戶剛修復的連接)
.3. 客戶端收到隨機串 E 后,和 secret 連接在一起做一次 hash (可以使用 md5 算法) H,回應服務器。這可以讓服務器校驗客戶端是否真的擁有 secret 。
.4. 服務器收到二次握手信號 H 后,用同樣的 hash 算法做一次 secret 校驗,確認是合法的客戶端后繼續通訊;若是非法連接則立即斷開。
.5.1 如果是新連接,那么服務器利用得到的 secret 初始化 RC4 加密算法需要的 s-box ,之后的通訊利用 RC4 算法加密。
.5.2 如果是舊連接修復,那么服務器將客戶端未收到的數據包重發一次。并從舊通道上復制 RC4 所用的 s-box 以及 secret 用于后續通訊。
.6. 此后的每次數據通訊,在數據打包后,都利用 RC4 算法做一次加密,并利用數據更新數據指紋(可以用加密后的數據流的 CRC 值)。每個數據包都記錄當前的指紋,并 cache 最近發送的 128 個數據包用于事后的連接修復。
.7. 設定一個超時時間,定期清理沒有數據來往的 TCP 連接。
這個協議的好處是,客戶端在握手完成后,任何時間都可以向服務器發起一個新的 TCP 連接取代舊的連接(無須利用舊連接是否還有效),而對應用層來說,連接重來沒有中斷過。
應用層可以做一些配合工作:比如設計一分鐘一次的心跳,如果長時間沒有收到心跳包,就主動發起新的 TCP 連接去取代舊的。這對無線網絡能增加網絡的穩定性。比如你切換 wifi 網絡時,由于 IP 地址的變化, TCP 連接不可能保持,但這套協議可以幫助你自動修復它。在沒有 MPTCP 支持時,它還可以盡量去使用更高質量的網絡(只要重新連接時去嘗試新的網絡設備即可)。
我計劃在?skynet?中實現一個和網絡 API 無關的 C 模塊作為中間層來完成以上工作:
接口大約是這樣:
struct socket_pool;// when sz == 0 and buffer == NULL, fd is closed // when sz > 0 and buffer != NULL, buffer is the data struct socket_package {int fd;int sz;const char * buffer; };struct socket_pool * socketpool_new(); void socketpool_release(struct socket_pool *sp); void socketpool_timeout(struct socket_pool *sp);void socketpool_pushinput(struct socket_pool *sp, int fd, const void * buffer, int sz); void socketpool_pushoutput(struct socket_pool *sp, int id, const void * buffer, int sz);int socketpool_popoutput(struct socket_pool *sp, struct socket_package *p); int socketpool_popinput(struct socket_pool *sp, struct socket_package *p);void socketpool_closefd(struct socket_pool *sp, int fd); void socketpool_close(struct socket_pool *sp, int id);fd 是底層的 socket handle ,id 是應用層的連接 id 。
當網絡層有任一 fd 收到數據時,通過 pushinput 接口把數據推送到 socketpool 中。調用 popinput 會報告哪個 id 上有新的數據包(或是沒有新的數據包)。
向一個 id 寫數據只需要調用 pushoutput ,然后反復調用 popoutput 可以得到真正需要將哪些數據寫入具體的 fd ,把它們交給網絡層 API 發送即可。
如果有 fd 斷開,或向主動關閉 id ,可以調用 closefd / close ;而 popoutput 則有可能收到一個 fd 關閉的信號,然后調用網絡層 API 去對應的 fd 即可。
這個模塊會處理數據打包加密,修復連接重新補發包等問題,并將這些隱藏在實現中。
2 月 14 日補充:
我實現了一個開源版本, API 有所不同。目前尚未仔細測試,有興趣的同學可以一起來完善它。
總結
以上是生活随笔為你收集整理的在移动网络上创建更稳定的连接的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java习题练习:杨辉三角
- 下一篇: Warning: Failed prop