最近項目的開發工作較少,因此有時間能搗鼓自己的東西。於是花了大概兩個星期的時間,粗略的搭起了一個游戲服務器的框架。
對我而言重復造此輪子的意義有:
(1)在經歷過一個上線游戲項目的洗禮之后,作為對這一年的開發工作、技術學習的一個總結,將自己這一年來所學所得所思所想,通過代碼表達出來。
(2)作為一個對工作中不會接觸到的一些新工具的使用的探索。項目組使用的技術已經是經過了時間的檢驗,並且趨於穩定。但是作為一個程序員,探索技術、保持學習能力乃是天職亦是樂趣。
在我看來,在實現一個游戲服務器之前,有必要先對其架構做一個分析。規划好哪些工作交給引擎、哪些工作交給腳本實現。由於我目標是實現一個放置類的數值游戲(類似於戰艦少女R),先可以粗略的認為沒有很高的性能指標。於是,我可以大力簡化我的服務器代碼,在引擎里只做核心且必要的工作,其它的包括游戲玩法邏輯統統交給腳本層實現。保證引擎代碼的精簡、干凈是十分有益的。
總的來說,我認為有必要放在引擎里的功能有以下這些:
(1)負責收發消息,解包封包的網絡功能。
(2)內嵌Python虛擬機的腳本功能。
(3)開放給腳本層的一系列注冊事件觸發的功能。比如消息事件觸發、邏輯事件觸發、超時事件觸發、定時事件觸發等。
其它的功能,包括連接管理、角色管理、戰斗等功能全部由腳本實現。
通過上述的簡要分析,決定采用有常見的單進程多線程模型,服務器進程一共有3個線程,分別是:
(1)網絡線程(socket server)。使用libevent網絡庫實現,其工作是將從網絡收到的消息包傳遞給邏輯線程、將邏輯線程投遞來的寫請求轉換成網絡消息包並發送出去。
(2)邏輯線程(game server)。這部分包括了內嵌python虛擬機、消息事件觸發模塊(msg)、邏輯事件觸發模塊(event)、超時事件觸發模塊(timeout)、定時事件觸發模塊(datetime)。
(3)日志線程(logger)。由於需要同步寫日志、並且日志模塊邏輯較為獨立,因此單獨獨立出一個線程。
線程之間通信也是一個需要考慮的問題。而我的做法是,所有線程之間的通信都是基於消息的傳遞的:網絡線程收完一個完整的消息包之后要通知邏輯線程,則將網絡消息包的內容打包投遞到邏輯線程的消息隊列里。邏輯線程、網絡線程需要打log則投遞一個請求到logger線程的消息隊列。這里說是投遞消息,其實實現上是投遞一個指向消息的指針。對消息的內存的管理方式是使用人為的約定,定義好消息指針的所有權,而非使用智能指針。規定由消息的的產生方負責申請消息所使用的內存,並將其指針傳遞給處理方。消息處理方在處理完一條消息之后,有責任要負責回收這塊消息的內存。
邏輯線程請求發網絡消息這個功能在實現上則稍有不同,不能做成簡單的向消息隊列投遞消息。因為網絡線程的事件循環是交給libevent控制的,因此不能實現成等待從隊列里取消息。暫且做成讀寫一個本地互聯的socket(類似於self pipe trick的變種),我稱之為control socket。因此網絡線程(socket server)管理兩種不同的socket,分別是data socket和control socket。data socket是服務器與客戶端連接的socket,control socket則是用於服務器內部線程通信的socket,負責傳遞邏輯線程對網絡線程的請求,包括邏輯線程的請求發包、請求中斷連接、請求主動連接等。
以上便是這個游戲服務器引擎實現的大體描述。今后會繼續寫相關博客記錄后續相關模塊的開發過程、遇到的問題、踩過的坑等。先寫下此文以示決心:)
============================================
更新