C++中訊息自動派發之一 About JSON

來源:互聯網
上載者:User
文章目錄
  • 1. 閑序
  • 2. 定義idl檔案
  • 3. 使用idl 代碼產生器產生訊息定義c++ 標頭檔
  • 4. 使用產生的C++ 訊息標頭檔
  •  5. 邏輯層處理訊息
  • 6. More 
1. 閑序

  遊戲伺服器之間通訊大多採用非同步訊息通訊。而訊息打包常用格式有:google protobuff,facebook thrift, 千千萬萬種自訂二進位格式,和JSON。前三種都是二進位格式,針對C++開發人員都是非常方便的,效率和包大小(資料冗餘度)也比較理想。而JSON是字串協議,encode和decode需要不小的開銷。500位元組json字串解析大約需要1ms左右。JSON在指令碼語言中非常常見,比如WEB應用、Social Game等,原因是web應用通過多進程分攤了JSON解析的CPU開銷,而且這些應用即時性不強。JSON相對於二進位協議有點就是它是自描述的,調試JSON訊息非常的方便,如果訊息出錯簡單的將訊息log到檔案,肉眼即可分辨真偽(眼力不行,有工具相幫http://www.jsoneditoronline.org/,更多工具參見http://json.org/)。事實上json由於是字串,壓縮傳輸也可以達到比較理想的壓縮比。

  我們的Social Game 用戶端都是Flash,Flash 工程師們非常喜歡使用Json,幾款遊戲Flash和Php通訊都是使用Json。新的遊戲支援即時對戰,後台使用c++實現,我們仍然採用JSON。在後台計算時為了保證即時性,我們一般把json解析放到網路線程(多線程),解析成c++的struct 特定類型再post到邏輯線程(單線程)處理。這樣Json的解析可以分攤到多個CPU上,並且不浪費主邏輯線程cpu。

  目前遇到的問題是,如果每增加一個介面,就增加一個struct,再在網路處理邏輯函數中增加json解析代碼(包括錯誤處理),再跟flash聯調協議。還有一個挺煩人的時介面文檔每次都要更新,如果直接把定義struct的標頭檔給flash,但是貌似不太優雅,還是有份文檔比較正式。

  我參考了一下google protobuf 和 facebook thrift,想設計如下訊息定義方式。

2. 定義idl檔案

  interface description language ?其實我只有訊息格式描述,並無介面,但是idl比較容易接受。

  假如說需要一個訊息描述student的資料,那麼使用 我定義idl描述其內容如下,student.idl

//! 定義student訊息格式,版本號碼1
stuct student_t(version=1)
{
//! 描述student需要子類型book
  struct book_t(version=1)
{
//! book中包含兩個欄位,ages 16位元字,content字串,可為空白,預設值為”oh nice“
        int16  pages;
      string content(default="Oh Nice!");
 }
//! 定義年齡,分數,姓名,都是基本類型
//! 定義friends為數組,單項類型為字串,對應json數組
//! 定義books為字典,key為字串,項為book結構,對應json對象結構
  int8 age;
  float grade(default=0);
  string name;
  array<string> friends;
  dictionary<string, book_t> books;
};
3. 使用idl 代碼產生器產生訊息定義c++ 標頭檔

   idl_generator.py student.idl -l cpp -o msg_def.h

      產生msg_def.h

      idl_generator.py@ http://ffown.googlecode.com/svn/trunk/fflib/lib/generator/

4. 使用產生的C++ 訊息標頭檔

  產生的標頭檔內容是:

  

struct student_t
{
struct book_t
{
int16_t pages;
string contents;
};
int8_t age;
float grade;
string name;
vector<string> friends;
map<string, book_t> books;
};
//! 模板類,T為回調物件類型,每種msg 類型T中都需要定義相應的handle函數, R代表請求的socket類型指標,這裡使用泛型表示
template<typename T, typename R>
class msg_dispather_t
{
typedef runtime_error msg_exception_t;//!請求格式出錯,拋出異常
typedef rapidjson::Document json_dom_t;   //! 使用rapidjson庫實現json解析,但是某個時刻可能替換該庫,故typedef
typedef rapidjson::Value json_value_t; //! rapidjson原始碼:http://code.google.com/p/rapidjson/
typedef R socket_ptr_t;  //! 請求socket
typedef int (msg_dispather_t<T, R>::*reg_func_t)(const json_value_t&, socket_ptr_t); //! 訊息對應的解析函數
public:
msg_dispather_t(T& msg_handler_):
m_msg_handler(msg_handler_)
{
m_reg_func["student_t"] = &msg_dispather_t<T, R>::student_t_dispacher;//! 所有的訊息都在構造時註冊解析函數,解析函數是通過idl自動產生的
}
int dispath(const string& json_, socket_ptr_t sock_);//! 介面函數,使用者只需單點接入dispatch,訊息會自動派發到msg_handler特定的handle函數

private:
int student_t_dispacher(const json_value_t& jval_, socket_ptr_t sock_)//! 每個訊息都會自動產生特定的訊息解析函數,首碼為訊息名稱
{
student_t s_val;
const json_value_t& age = jval_["age"];
const json_value_t& grade = jval_["grade"];
const json_value_t& name = jval_["name"];
const json_value_t& friends = jval_["friends"];
const json_value_t& books = jval_["books"];
char buff[512];

if (false == age.IsNumber())
{
snprintf(buff, sizeof(buff), "student::age[int] field needed");
throw msg_exception_t(buff);
}
s_val.age = age.GetInt();
if (false == grade.IsDouble())
{
snprintf(buff, sizeof(buff), "student::grade[float] field needed");
throw msg_exception_t(buff);
}
s_val.grade = grade.GetDouble();
if (false == name.IsString())
{
snprintf(buff, sizeof(buff), "student::name[string] field needed");
throw msg_exception_t(buff);
}
s_val.name = name.GetString();
if (false == friends.IsArray())
{
snprintf(buff, sizeof(buff), "student::friends[Array] field needed");
throw msg_exception_t(buff);
}
for (rapidjson::SizeType i = 0; i < friends.Size(); i++)
{
const json_value_t& val = friends[i];
if (false == val.IsString())
{
snprintf(buff, sizeof(buff), "student::friends field at[%u] must string", i);
throw msg_exception_t(buff);
}
s_val.friends.push_back(val.GetString());
}
if (false == books.IsObject())
{
snprintf(buff, sizeof(buff), "student::books[Object] field needed");
throw msg_exception_t(buff);
}
rapidjson::Document::ConstMemberIterator it = books.MemberBegin();
for (; it != books.MemberEnd(); ++it)
{
student_t::book_t book_val;
const json_value_t& name = it->name;
if (false == name.IsString())
{
snprintf(buff, sizeof(buff), "student::books[Object] key must [string]");
throw msg_exception_t(buff);
}

const json_value_t& val = it->value;
if (false == val.IsObject())
{
snprintf(buff, sizeof(buff), "student::books[Object] value must [Object]");
throw msg_exception_t(buff);
}

const json_value_t& book_pages = val["pages"];
const json_value_t& book_contens = val["contents"];
if (false == book_pages.IsNumber())
{
snprintf(buff, sizeof(buff), "student::books::pages[Number] field needed");
throw msg_exception_t(buff);
}
book_val.pages = book_pages.GetInt();
if (false == book_contens.IsString())
{
snprintf(buff, sizeof(buff), "student::books::book_contens[String] field needed");
throw msg_exception_t(buff);
}
book_val.contents = book_contens.GetString();
s_val.books[name.GetString()] = book_val;
}

m_msg_handler.handle(s_val, sock_);//! 由於msg_handler中重載了針對所有訊息的handle函數,此函數會被正確的派發到邏輯層
return 0;
}

private:
T& m_msg_handler;
map<string, reg_func_t> m_reg_func;
};

template<typename T, typename R>
int msg_dispather_t<T, R>::dispath(const string& json_, socket_ptr_t sock_)
{
json_dom_t document; // Default template parameter uses UTF8 and MemoryPoolAllocator.
if (document.Parse<0>(json_.c_str()).HasParseError())
{
throw msg_exception_t("json format not right");
}
if (false == document.IsObject() && false == document.Empty())
{
throw msg_exception_t("json must has one field");
}

const json_value_t& val = document.MemberBegin()->name;
const char* func_name = val.GetString();
typename map<string, reg_func_t>::const_iterator it = m_reg_func.find(func_name);

if (it == m_reg_func.end())//! 尋找解析派發函數是否存在
{
char buff[512];
snprintf(buff, sizeof(buff), "msg not supported<%s>", func_name);
throw msg_exception_t(buff);
return -1;
}
reg_func_t func = it->second;

(this->*func)(document.MemberBegin()->value, sock_);
return 0;
}

 

 5. 邏輯層處理訊息

  邏輯層不需要編寫繁雜的json解析和錯誤處理,只要沒有觸發異常,訊息會自動派發到msg_handler中的handle函數,所以邏輯層只需針對每一個訊息類型

都重載一個handle函數即可,樣本處理代碼如下:

class msg_handler_t
{
public:
typedef int socket_ptr_t;
public:
void handle(const student_t& s_, socket_ptr_t sock_)
{
cout << "msg_handler_t::handle:\n";
cout << "age:" << int(s_.age) << " grade:" << s_.grade << " friends:"<< s_.friends.size() << " name:"
<< s_.name << " books:" << s_.books.size() <<"\n";
}
};

int main(int argc, char* argv[])
{
try
{
string tmp = "{\"student_t\":{\"age\":123,\"grade\":1.2,\"name\":\"bible\",\"friends\":[\"a\",\"b\"],"
"\"books\":{\"bible\":{\"pages\":123,\"contents\":\"oh nice\"}}}}";
msg_handler_t xxx;
msg_dispather_t<msg_handler_t, msg_handler_t::socket_ptr_t> p(xxx);
p.dispath(tmp, 0);
}
catch(exception& e)
{
cout <<"e:"<< e.what() <<"\n";
}
}
範例程式碼: http://ffown.googlecode.com/svn/trunk/fflib/lib/generator/6. More 

  1> json解析目前使用 rapidjson,號稱效率極佳,此處用它最大的好處是只需包含標頭檔即可使用

  2> 分析解析idl 檔案程式使用python編寫(正在編寫中)

  3> idl 定義中支援namespace 為佳,但考慮複雜性,第一版本暫不支援。

    4> 本篇只實現了json to struct,實際上 struct to struct 也很容易實現,json 字串的第一個字元為'{',而如果採用二進位訊息,第一個字元表示訊息類型的字串長度(一個位元組足以),如"sdudent_t",那麼首位元組應該為9,並且設定首位元組首位為1,那麼描述類型的字串長度最大為128個字元(足以了)。放到下篇再搞,睡了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.