侯爍
(杭州碧游信息技術(shù)有限公司 浙江杭州 310012)
傳統(tǒng)TCP協(xié)議運(yùn)用在移動(dòng)設(shè)備游戲上所遇到的問(wèn)題。與有線網(wǎng)絡(luò)的情況不同,有線網(wǎng)絡(luò)的丟包(Packet Loss)可以被一個(gè)正態(tài)分布隨機(jī)過(guò)程來(lái)描述,即在丟包不存在時(shí)間相關(guān)性隨機(jī)事件。而無(wú)線網(wǎng)絡(luò)的丟包往往可以描述成有兩個(gè)狀態(tài)的馬爾可夫過(guò)程[1-2]。當(dāng)進(jìn)入平穩(wěn)期時(shí),丟包會(huì)呈現(xiàn)為一個(gè)正態(tài)分布過(guò)程、但是丟包概率還是遠(yuǎn)大于有線傳輸;而進(jìn)入屏障期時(shí),鏈路中傳輸?shù)臄?shù)據(jù)包會(huì)出現(xiàn)完全損失。移動(dòng)網(wǎng)絡(luò)還具有一個(gè)異構(gòu)性特點(diǎn),即用戶會(huì)在不同的網(wǎng)絡(luò)環(huán)境下進(jìn)行切換。
目前,TCP 協(xié)議一直是被最廣泛使用的實(shí)現(xiàn)網(wǎng)絡(luò)游戲客戶端與服務(wù)端可靠通信的協(xié)議。但是在TCP協(xié)議設(shè)計(jì)時(shí)還沒有出現(xiàn)移動(dòng)網(wǎng)絡(luò),TCP 是完全按照有線網(wǎng)絡(luò)的設(shè)計(jì)思路下的產(chǎn)物,因此直接在TCP 協(xié)議之上設(shè)計(jì)移動(dòng)端網(wǎng)絡(luò)游戲的通信架構(gòu),會(huì)帶來(lái)以下幾個(gè)方面的問(wèn)題。
(1)在雙方互不發(fā)送信息時(shí),即使網(wǎng)絡(luò)層已經(jīng)發(fā)生改變無(wú)法通信,沒有收發(fā)信息造成的網(wǎng)絡(luò)層錯(cuò)誤信息(SocketError),不能立刻判斷出現(xiàn)了斷線的狀態(tài),因此很多網(wǎng)絡(luò)架構(gòu)中如果長(zhǎng)時(shí)間沒有網(wǎng)絡(luò)交互的情況下,會(huì)定期發(fā)送PING 消息來(lái)相互確認(rèn)網(wǎng)絡(luò)狀態(tài),因此PING 消息的間隔決定了基于TCP 協(xié)議的網(wǎng)絡(luò)層斷線重連的敏感度和反應(yīng)速度,但即使每秒發(fā)送一次PING也會(huì)讓玩家感受到明顯的卡頓,而過(guò)高頻率的PING消息則消耗了網(wǎng)絡(luò)和服務(wù)器計(jì)算資源。而過(guò)于靈敏的PING機(jī)制會(huì)在移動(dòng)網(wǎng)絡(luò)進(jìn)入短暫的屏障期后,由于沒有收到PING消息對(duì)斷線進(jìn)行“誤判”,從而對(duì)用戶的體驗(yàn)進(jìn)行打斷。
(2)在移動(dòng)端設(shè)備經(jīng)常出現(xiàn)的網(wǎng)絡(luò)切換后,由于源地址變更后TCP 協(xié)議無(wú)法收到ACK,不能確定對(duì)方是否收到在斷線之前發(fā)送的數(shù)據(jù)包,這樣“可靠”的連接在發(fā)生斷線之后就變得“不可靠”了。因此,很多移動(dòng)端游戲在發(fā)生斷線TCP重連后往往需要進(jìn)行打斷當(dāng)前游戲體驗(yàn)進(jìn)行重新登錄操作,重新“可靠的”讓客戶端與服務(wù)端的數(shù)據(jù)進(jìn)行一次同步。而這樣的體驗(yàn),對(duì)于在公交車或者地鐵上使用的用戶來(lái)說(shuō)是不可接受的。
(3)無(wú)線網(wǎng)絡(luò)在穩(wěn)定期丟包是因?yàn)殡S機(jī)信號(hào)干擾導(dǎo)致的隨機(jī)丟包。而TCP協(xié)議由于設(shè)計(jì)之初視為有線網(wǎng)絡(luò)服務(wù),擁塞控制認(rèn)為丟包意味著網(wǎng)絡(luò)擁塞,需要禮貌性地“慢啟動(dòng)”縮小發(fā)送窗口進(jìn)行退讓[3]。而且TCP的收發(fā)機(jī)制由于是在操作系統(tǒng)內(nèi)核態(tài)中進(jìn)行處理,程序無(wú)法對(duì)其進(jìn)行控制,當(dāng)出現(xiàn)丟包后,必須等待ACK超時(shí)機(jī)制進(jìn)行重發(fā)而程序無(wú)法干預(yù)。這些問(wèn)題導(dǎo)致TCP 在無(wú)線信道環(huán)境下延遲較高,且無(wú)法持續(xù)達(dá)到最高速率進(jìn)行傳輸。
之所以出現(xiàn)上述問(wèn)題,是因?yàn)門CP 將端對(duì)端的傳輸以及擁塞控制,以及可靠傳輸這兩個(gè)功能,耦合在了一個(gè)協(xié)議中。而這個(gè)協(xié)議被操作系統(tǒng)層實(shí)現(xiàn),代碼無(wú)法控制。在客戶端和服務(wù)端的進(jìn)程都正常運(yùn)行時(shí),即使出現(xiàn)了斷網(wǎng)現(xiàn)象,雙方的可靠傳輸機(jī)制中的數(shù)據(jù)包序號(hào)、超時(shí)重傳機(jī)制仍然有效,可以在網(wǎng)絡(luò)層將數(shù)據(jù)包傳送到對(duì)方之后繼續(xù)工作。KCP協(xié)議是一個(gè)運(yùn)行在會(huì)話層的,負(fù)責(zé)通過(guò)在其KCP 包頭的CONV 字段(會(huì)話號(hào))進(jìn)行會(huì)話的唯一性標(biāo)識(shí),與TCP可靠傳輸原理類似的可靠傳輸協(xié)議,但是它并不關(guān)注數(shù)據(jù)是如何被傳輸?shù)?。而UDP協(xié)議只負(fù)責(zé)端到端的數(shù)據(jù)傳輸,但是它不關(guān)心數(shù)據(jù)是否能夠被對(duì)方收到。將KCP 運(yùn)行在UDP之上,類似實(shí)現(xiàn)了將TCP 協(xié)議的端對(duì)端傳輸與可靠性保證兩個(gè)功能做了解耦。
但是這樣的設(shè)計(jì)并沒有TCP 的“三次握手”,客戶端無(wú)法自發(fā)“優(yōu)雅”地與服務(wù)端協(xié)商會(huì)話號(hào)從而建立連接,因此在建立連接時(shí)客戶端與服務(wù)端需要先通過(guò)第三方使用TCP或者HTTP協(xié)議協(xié)商會(huì)話號(hào),在會(huì)話號(hào)被同步到客戶端與服務(wù)端之后才能進(jìn)行通信,建立連接流程具體見圖1。

圖1 LoginServer協(xié)助KCP協(xié)商會(huì)話號(hào)Conv,并與服務(wù)端通信
同樣由于這個(gè)過(guò)程沒有“四次揮手”,因此在長(zhǎng)時(shí)間收不到對(duì)方消息的情況下,一方面可能是網(wǎng)絡(luò)的確長(zhǎng)時(shí)間存在問(wèn)題,另一方面可能是對(duì)方進(jìn)程已經(jīng)退出所以我方也應(yīng)該進(jìn)行退出操作。在無(wú)法判斷這兩種情況下,仍然需要借助第三方通過(guò)TCP或者HTTP協(xié)議進(jìn)行對(duì)方狀態(tài)的查詢,具體見圖2。

圖2 UDP端口收不到消息,向LoginServer查詢服務(wù)端狀態(tài)
由于UDP 的無(wú)連接屬性[4],客戶端無(wú)論由于網(wǎng)絡(luò)切換造成源地址變換,還是客戶端鏈通信恢復(fù)后,不需要像TCP那樣先要發(fā)現(xiàn)網(wǎng)絡(luò)斷開、再重新發(fā)送“三次握手”請(qǐng)求,即可相互發(fā)送數(shù)據(jù),大大降低了傳輸層恢復(fù)的速度。而只要通信雙方的KCP 連接對(duì)象存活,KCP層都可以通過(guò)超時(shí)重發(fā)機(jī)制進(jìn)行可靠的通信。而且由于KCP 數(shù)據(jù)包是在用戶程序?qū)用嫔傻?,程序控制利用UDP發(fā)送KCP數(shù)據(jù)包的頻率,可以設(shè)計(jì)更加適合無(wú)線信道環(huán)境的擁塞控制機(jī)制,甚至可以通過(guò)一次發(fā)送對(duì)一個(gè)KCP 數(shù)據(jù)包多次發(fā)送的方法,來(lái)抵抗隨機(jī)丟包重發(fā)造成的延時(shí)。
一個(gè)經(jīng)典的MMO 服務(wù)端,KBEngine,網(wǎng)絡(luò)架構(gòu)圖可以由圖3 來(lái)表示[5-6],客戶端通過(guò)服務(wù)端的Gate 進(jìn)程與服務(wù)端的Game 進(jìn)程中的實(shí)體進(jìn)行通信。Gate 進(jìn)程會(huì)維護(hù)每個(gè)與之通信的服務(wù)端實(shí)體(Entity)所在的Game 進(jìn)程的路由表,當(dāng)服務(wù)端實(shí)體在Game 進(jìn)程之間遷移時(shí),服務(wù)端實(shí)體需要跟對(duì)應(yīng)Gate進(jìn)行相應(yīng)的維護(hù)。因此可以認(rèn)為,使用TCP協(xié)議與固定的Gate進(jìn)行通信,通過(guò)Gate路由之后,即可以跟Game進(jìn)程進(jìn)行雙向收發(fā)RPC消息。

圖3 KBEngine服務(wù)器框架
在引入KCP 連接的概念后,在對(duì)原有服務(wù)端架構(gòu)不進(jìn)行大規(guī)模修改的前提下,基于KCP 的服務(wù)端架構(gòu)由圖4可知,額外添加一個(gè)負(fù)責(zé)登錄和查詢的微服務(wù),并在Gate之外額外添加一層UDPGate進(jìn)程。

圖4 基于KCP的網(wǎng)絡(luò)游戲服務(wù)器框架
新的登錄流程可以用圖5 來(lái)表示,當(dāng)客戶端通過(guò)HTTP 與登錄微服務(wù)發(fā)送Login 請(qǐng)求并通過(guò)后,微服務(wù)隨機(jī)選擇一個(gè)Game 進(jìn)程生成一個(gè)實(shí)體,Game 實(shí)體向Gate 注冊(cè)后,由Gate 進(jìn)程生成一個(gè)KCP 對(duì)象并申請(qǐng)一個(gè)全服唯一的KCP 會(huì)話號(hào),并將KCP 會(huì)話號(hào)到對(duì)應(yīng)Gate 的路由信息寫入一個(gè)Redis 進(jìn)程,然后會(huì)話號(hào)和UDPGate 的IP 和端口通過(guò)TCP 的LoginSuccess 發(fā)送給客戶端完成正常登錄流程。

圖5 新的登錄流程
客戶端與服務(wù)端的RPC通信流程可以用圖6來(lái)表示。UDPGate進(jìn)程負(fù)責(zé)維護(hù)KCP的會(huì)話號(hào)到Gate進(jìn)程之間的路由和消息傳遞,當(dāng)UDPGate 收到一個(gè)UDP 帶有一個(gè)陌生的會(huì)話號(hào)時(shí),會(huì)去Redis進(jìn)程中查找對(duì)應(yīng)的Gate 進(jìn)程并將這個(gè)路由信息保存下來(lái),UDPGate 負(fù)責(zé)將收到的UDP 消息發(fā)送到對(duì)應(yīng)Gate 進(jìn)程,Gate 進(jìn)程持有KCP對(duì)象對(duì)UDP消息進(jìn)行解包,獲得原始的RPC消息,從而轉(zhuǎn)發(fā)到對(duì)應(yīng)的服務(wù)端實(shí)體進(jìn)行RPC調(diào)用。

圖6 客戶端與服務(wù)端的RPC通信流程
與現(xiàn)在大部分基于TCP協(xié)議的游戲服務(wù)器框架相比,這個(gè)服務(wù)器框架,通過(guò)在KCP層將傳輸層的斷線進(jìn)行隱藏,解決了上層業(yè)務(wù)邏輯被鏈路斷線所打斷的問(wèn)題,也可以給用戶帶來(lái)網(wǎng)絡(luò)無(wú)縫切換的體驗(yàn)。在這個(gè)框架下,客戶端可以通過(guò)與不僅僅一個(gè)UDPGate 進(jìn)行通信,在不登出游戲的情況下,客戶端可以進(jìn)行無(wú)縫的選擇在最快的接入點(diǎn)之間進(jìn)行切換。而且這個(gè)游戲服務(wù)器框架這是在原有服務(wù)器框架下進(jìn)行了一些進(jìn)程節(jié)點(diǎn)的添加,改動(dòng)量小,可以在現(xiàn)有服務(wù)器上進(jìn)行快速的部署升級(jí)。