小議遊戲服務器的消息組包

原文地址:http://www.verydemo.com/demo_c352_i7884.html

 

爲何要組包

首先一個原因避免讀寫頻率瓶頸:不管百兆還是千兆網卡,消息的收發對通訊io的調用都會產生中斷,而這個中斷限制了每秒不能無限制的寫入/讀出,目前網卡拋開系統和cpu瓶頸驅動級的測試瓶頸是60w/s,而筆者所用ace通訊框架在windows平臺下按前攝器(proactor)模式封裝的測試,讀寫瓶頸在接近10w/s。假設針對某個socket每秒有300條寫入消息(這個數字在廣播範圍內密集戰鬥中還屬中等要求),那麼拋開消息大小來說,這種頻率下能夠支持的同時讀寫的socket數也就300個!但如果能讓每80條消息組成1條,理論上支持的同時讀寫的socket數量也能上升80倍,接近2w了

另外一個原因節約冗餘的消息頭:tcp 和ip頭會佔用近20字節,加上你自定義的消息頭,加密,壓縮key值這種頭應該也會接近20字節了,如果信息的同步只佔用8字節,這冗餘的頭就相當於實際使用信息的5倍,還是拿每秒300條,每80條消息爲組包單位,則不組包要產生約40Bx300x8 ≈ 93kb的冗餘信息,2000個鏈接下就有180Mb的冗餘信息!!

Ok,不壓包的情況下只有2種選擇:1避免上規模的範圍廣播;2限制玩家鏈接數量…看上去對於遊戲服務器來說都不像是個好辦法

 

如何組包

理論上組包很簡單,就是把小個的信息壓到一個大個的消息塊裏,爲了拆包方便當然不可少還得把信息的類型也同時壓入,直到消息塊達到或接近消息塊長度限制然後發出去,還有個問題,如果壓入消息少遲遲沒有達到發送的長度,消息豈不一直等待,所以還需要個定時器按規定幀率檢查是否有壓入數據需要發出。

拆包要複雜點,因爲壓入的信息不同,所以當你搜到一個大消息的時候並不知道里面的信息如何分佈,有點類似底層通訊處理流信息時候的情況,得自己拆包(好在沒有處理粘包的情況),推薦的做法是給每種消息都對應一個消息類來處理接收的,在buff第一個字節判斷完類型後,偏移到數據段,拿對應的消息類指針去指向這段,如果實現的消息類有繼承體系存在虛表的偏移,則需要在類裏面存在一個數據指針(消息類如果處理髮送邏輯的話這個數據指針也是有用的),用這個指針去指數據段,消息類裏順帶一個處理的方法一併解決邏輯了。處理完一個消息了以後對buff進行剛剛所指數據結構大小的偏移,進行下一個消息的處理

 

什麼層次的組包

選擇在通訊底層組包還是邏輯上層組包。

在通訊層組包的優點很明顯,上層根本不用關心消息是以什麼形式發送出去的,就像應用層不去管Tcp底層協議到底怎麼個保證消息正確到達目的地。而這個組包的操作也很容易劃分到現有框架裏agent的功能中。理由很簡單,所有服務器發給客戶端的消息都通過這,在這相對容易在檢查時間內組上包,而且在這有消息的加解密處理,正好可以再把組拆包處理也放進去。這個方案也還是存在一些問題,首先agent要求消息的快速轉發,爲了滿足每個socket的並行處理,已經管理了不少線程,按上面描述的邏輯在原本的發送線程裏組包還要額外一個定時線程去檢查是否要發出待完成組包數據,這樣每個socket就都有收包組包發包3個線程形成了環形互斥,經過測試這樣會讓收發效率受到不小影響,其次針對性不強,在agent上組包是以socket爲單位,把各個服務器規定時段內發向該socket的消息組包,而組包消息之間的聯繫無法保證。假設情況1 有個消息需要及時發出,但是被agent攔下組包;假設情況2 有個消息後續還有個關聯的消息,本來想組一塊的,結果被另一個服務器消息插入到其間;假設情況3 有幾個消息有共同性,除了組包還可以去除一些冗餘信息;這些情況用agent來組包的方式並不容易實現。

描述了不少底層agent組包的侷限性也不難看出筆者傾向於選擇後一種做法:在邏輯層上組包。先說它的缺點吧:邏輯層要關心消息發送的手段區分處理及時發送和非及時發送的組包消息,這樣會讓邏輯層處理看上去不夠單純。其實作爲遊戲服務器面臨的通訊壓力絕大多數來自視野信息的同步消息,這種消息包括屬性改變,位置同步,戰鬥操作,物體創建等,而另外的功能交互型消息需要及時反饋的居多不太適合組包,查詢型消息的反饋一般信息已經足夠大了組不組包都一樣,聊天型消息一般有自己的壓縮邏輯甚至不走遊戲內的通訊連接也不適合組包。回頭來看視野信息同步消息,這種消息集中在場景邏輯裏,特點是每秒產生的數量多,單個信息小,關聯性大,需要範圍廣播,一切特點看來都非常適合組包。而一般場景邏輯每個玩家對象自身攜帶邏輯幀驅動,檢查等待發出的組包消息處理可以很方便的放在裏面驅動,對原本的服務器設計沒有任何影響。同時消息的緩衝也可以放在玩家對象上,利用一樣的內存管理策略不用擔心臨時變量的消耗。最後場景邏輯一般可以分爲多個zone服務器在處理,所以不用擔心組包操作對整體性能會產生多大的影響。發送實現起來也想當容易,只要在玩家類原有的發送接口上加個sendcachemsg,調用sendmsg的時候想想是不是需要及時發送就行了。

有人可能會有想法,組包能不能不放agent的通訊底層而放zone的通訊底層呢,這樣不用擔心agent轉發效率也不用勞煩寫邏輯的人費神。呃~好像不行,因爲zone通訊底層只有針對服務器內部的連接,而沒有針對某個客戶端socket的連接,從而也沒有組包的基礎單位了。這裏補充說一下服務器間的通訊消息沒必要組包因爲一般要麼通過127本地走要麼在一個機房走,而客戶端的請求消息組不組包就看遊戲的操作需求要不要求反應靈敏了。

 

組多大的包合適

相信看到這應該有“當然越大越好”這種想法,但實際情況並不如此。越大的包在複雜網絡環境下會意味着重發的概率越高,使得在內網測試的一些理想數據放到外網會有很大的差距。對於TCP協議來說,整個包的最大長度是由最大傳輸大小(MSS,Maxitum Segment Size)決定,MSS就是TCP數據包每次能夠傳輸的最大數據分段。爲了達到最佳的傳輸效能TCP協議在建立連接的時候通常要協商雙方的MSS值,這個值TCP協議在實現的時候往往用MTU值代替(需要減去IP數據包包頭的大小20Bytes和TCP數據段的包頭20Bytes)所以往往MSS爲1460。經過項目的測試,1024的包往往是效率最高的(2的階乘數是很神祕的,你懂的)

 

再補充幾句

windows平臺和linux平臺,選擇用tcp協議通信,默認情況下會調用Nagle算法,這個算法的核心和上面描述的東西差不都,爲的就是減少小包包頭對帶寬的佔用,但由於是針對鏈接的算法,本身適合的環境有限,如果短時間有很多小包往一個鏈接發送,這個算法無疑能起到很好的效果。但發送給客戶端的消息往往不是一個鏈接能搞定的,而且上層做了組包後包大小超過256,Nagle算法幾乎起不到作用反而空開了緩存白轉了些判斷邏輯。所以如果選擇上層組包的方式需要顯示調用關閉Nagle,在設置sock的時候改成TCP_NODELAY參數

 

發佈了70 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章