動機
網游服務器端開發過程中,很多控制游戲的參數都不應該直接硬編碼的。需要各種各樣的配置和腳本文件,好處:
- 可以由策划或數值去隨意修改,而不用動程序代碼
- 配置可以動態加載,可以動態改變服務器運行中的參數,對已經發布的功能進行調整
一般,可采用:
- ini配置,一般用於window下的軟件,游戲客戶端有時會用到。比較簡單,功能有限。
- Excel表格,數值策划特別喜歡用這個,可以做很多運算,生成數值,可以用VBA做更多的事情。
- xml配置,對於層次比較深、結構比較復雜的數據,應該算最佳選擇了。
XML(eXtensible Markup Language)是一種標記語言,用於說明數據是什么,以及攜帶數據信息。主要用於:
- 豐富文件(Rich Documents):自定文件描述並使其更豐富
- 元數據(Metadata):描述其它文件
- 配置文件(Configuration Files):設定應用程序的參數
下面主要介紹一下對於xml文件作為服務器配置時候的解析方案。
問題
解析下面的XML文件:
<config> <node1 prop1="100" prop2="i am string", prop3="2012-01-02 23:00:00"/> <node2 id="1" prop1="100" prop2="string1"/> <node2 id="2" prop1="100" prop2="string1"/> <node2 id="3" prop1="100" prop2="string1"/> <node2 id="4" prop1="100" prop2="string1"/> <node3 prop1="100" prop2="string1"/> <node3 prop1="100" prop2="string1"/> <node3 prop1="100" prop2="string1"/> <node3 prop1="100" prop2="string1"/> </config>
- node1 – 整個xml文件里面只有一個該節點
- node2 – 有多個並且id屬性可以作為它的鍵值,稱之為節點map
- node3 – 有多個名為node3的節點,但沒有鍵值,稱之為節點vector
一般的解決方案
使用XMLPaser(用libxml2封裝的一個解析器)來解析(TinyXML也類似,DOM方式的都大同小異):
XMLPaser xml; if (xml.initFile("xxx.xml")) { xmlNodePtr root = xml.getRootNode("config"); if (root) { // 解析node1的prop1和prop2屬性 struct NodeConfig{ int prop1; string prop2; } config; xmlNodePtr node1 = root->getChildNode(root, "node1"); if (node1) { node1->getNodePropNum(node1, "prop1", &config.prop1, sizeof(config.prop1)); node2->getNodePropStr(node1, "prop2", config.prop2); } // 解析node2節點map struct NodeConfig{ int prop1; string prop2; }; std::map<int, NodeConfig> nodemap; xmlNodePtr node2 = root->getChildNode(root, "node2"); while (node2) { int id; NodeConfig config; node2->getNodePropNum(node2, "id", &id, sizeof(id)); node2->getNodePropNum(node2, "prop1", &config.prop1, sizeof(config.prop1)); node2->getNodePropStr(node2, "prop2", config.prop2); nodemap[id] = config; node2 = node2->getNextNode(node2, "node2"); } // 解析node3節點vector ..... } }
壞味道分析
上面的代碼,有幾點不足之處,列舉如下:
- 代碼重復
- 整個解析過程大同小異,一步一步遍歷加載在內存中的節點樹
- 節點或節點屬性的名稱、節點的層次結構不同的時候,就得寫不同的代碼,一般會采用復制代碼的方式
- 使用不便
- 往往要寫一個單件管理器,在服務器啟動的時候加載該配置,然后在管理器里面把需要的數據結構都定義好
- 使用的時候,引用管理器里面的成員變量,代碼既丑陋又容易出錯
- 不安全
- 節點名稱、屬性名稱都是字符串,拼錯了,運行時會發生邏輯錯誤
更好的解決方案
C++的結構與XML的對應樹狀結構對應起來,也就是數據綁定方案(Xml Data Binding)。自己曾經實現過一個Xml Data Binding庫,名為xml_parser。具體用法如下:
step1: 編寫一份描述XML結構的配置文件(也是一份XML文件,xml_parser.xml)
<config> <node1 prop1="int" prop2="string", prop3="t_Date"/> <node2 id="int" prop1="int" prop2="string" container_="map" key_="id"/> <node3 prop1="int" prop2="string" container_="vector" /> </config>
step2: 生成binding類
xmlpg -f xml_paser.xml -o xml_parser.h
step3: 應用程序中使用
xml_config<xml_paser> xml; if (xml.load("xxx.xml")) { // node1的prop1和prop2屬性 int prop1 = xml.node1.prop1(); string prop2 = xml.node1.prop2(); t_Date date = xml.node1.prop3(); // node2節點map for (xml_paser::Node2MapIter it = xml.node2.begin(); it != xml.node2.end(); ++ it) { int id = it->first; int prop1 = it->second.prop1(); string prop2 = it->second.prop2(); } // node3節點vector for (size_t i = 0; i < xml.node3.size(); i ++) { int prop1 = xml.node3[i].prop1(); string prop2 = xml.node3[i].prop2(); } }
更多解決方案
方式 | 特征 | 開源庫 |
DOM(Document Object Model) |
|
|
SAX(Simple API for XML) |
|
|
Data Binding |
|
|
XML與Excel表格做配置的比較
比較 | XML | Excel表格 |
結構 | 樹狀的層次結構 | MxN的二維數組 |
適用性 |
|
|
不足之處 |
|
|
2012/04/25 21:15 於上海