【C++中訊息自動派發之四】使用IDL構建Chat Server

來源:互聯網
上載者:User
文章目錄
  • 1. 情境設定
  • 2. 伺服器模組設計
  •  3. 總結

  前一篇blog 講了如何?IDL 解析器,本篇通過IDL解析器構建一個聊天伺服器程式。本程式用來測試IDL解析器的功能,網路層使用前邊blog中介紹的ffown庫。我們只需定義chat.idl檔案,idl解析器自動產生訊息排放代碼,省了每次再去繁瑣的編寫訊息解析、判斷代碼。

  IDL解析器介紹:http://www.cnblogs.com/zhiranok/archive/2012/02/23/json_to_cpp_struct_idl_parser_second.html

  ffown socket庫:http://www.cnblogs.com/zhiranok/archive/2011/12/24/cpp_epoll_socket.html

1. 情境設定

  1>. user 登入系統,檢查是否重登陸,若登陸過則返回出錯(由於無passwor認證,只好採用”搶注“方式,uid搶先登入者可登入)。user登入後須擷取線上的使用者ID列表。同時該user上線訊息也應該推送給線上的其他使用者。

  2>. user 登出,從伺服器中刪除使用者資訊,關閉socket。廣播給所有線上使用者該使用者下線。

  3>. chat 聊天。使用者可以給線上的某個使用者發送聊天資訊,也可以多人聊天,甚至可以給所有人廣播。

2. 伺服器模組設計

  

 

  1>. 網路層

    開發網路程式必須有一個穩定、高效的網路程式庫架構。目前流行的基於C++的網路程式庫有:

    a. Boost ASIO

    b. Libevent

    c. unix socket API

    這裡極力推薦ASIO,兩年來開發的多個伺服器程式都是基於ASIO實現的,自己也非常的熟悉。自己也閱讀過ASIO的源碼,收穫了一些非常寶貴的非同步IO的設計技巧。網上有些人評論ASIO太大,太臃腫,我覺得其實不然。雖然ASIO為實現跨平台而增加了很多封裝、宏,但是ASIO對應SOCKET的封裝還是比較簡單的。ASIO中最巧妙的就是所有IO模型都是建立在io_service上,這樣網路層非常容易使用多線程。針對ASIO的分析詳見前邊的blog:http://www.cnblogs.com/zhiranok/archive/2011/09/04/boost_asio_io_service_CPP.html。使用ASIO還有一個好處是,你可以充分享受Boost庫(如Lamda、shared_ptr、thread)帶來的便捷,生產力立刻提升一個台階。個人覺得使用ASIO需要有一定的模式基礎。我也是用ASIO封裝過一個網路層參見:

http://www.cnblogs.com/zhiranok/archive/2011/12/18/ffasio.html

    當然喜歡搞底層的工程師都愛自己構建一個socket通訊庫,這也無可厚非(即使有點重複造輪子),畢竟這樣個人或者團隊可以完全控制碼庫的品質,出了問題也容易排查,而且也不需要太大的工作量。使用ASIO時我們就出現過問題,1.39版本的asio非同步串連有bug,有非常小的機率回呼函數不能被調用(大並發測試),更新到1-44就ok了。個人認為,對於一個團隊,一個成熟的網路架構是成功的基石。

    本樣本中網路層傳輸協議非常簡單,訊息體body的長度(字串形式)+\r\n + 訊息體body,這樣可以直接使用telnet測試本程式。

  2>. 訊息派發層

    我曾使用過google protocol和facebook thrift,protocol只是封裝了訊息封裝,不具有訊息派發功能,thrift實際上是一個rpc架構,自動能夠產生client代碼或non blocking server架構代碼。但是我們開發即時線上遊戲背景程式都是基於訊息的,所以開發一個類似protoco這樣的東東還是很有意義的。用法是編寫訊息的idl檔案,定義請求訊息格式和響應訊息格式。idl檔案實際上也扮演了和client的介面描述文檔角色。接下來使用idl 解析器分析idl 自動產生訊息派發代碼。

    如在chat server樣本中,我定義了chat.idl, 產生訊息派發架構代碼的方式是:

    idl_generator.py idl/chat.idl include/msg_def.h

    產生的程式碼檔案為msg_def.h

    其中idl檔案定義為:

    

struct login_req_t
{
uint32 uid;
};

struct chat_to_some_req_t
{
array<uint32> dest_uids;
string content;
};

struct user_login_ret_t
{
uint32 uid;
};

struct user_logout_ret_t
{
uint32 uid;
};

struct online_list_ret_t
{
array<uint32> uids;
};

struct chat_content_ret_t
{
uint32 from_uid;
string content;
};

 

  3> 領域邏輯層

    領域邏輯盡量保證跟需求分析中建立的模型一致,DDD驅動。所以盡量不要整合太多網路層或訊息解析層的代碼。我的思路是將訊息解析用idl解析器實現,網路層使用成熟的架構,這樣我們只需集中精力測試邏輯層的正確即可。

    本chat server只是要測試一下idl 解析器的功能,所以沒有整合太多功能。

     主要程式碼片段為:

    

int chat_service_t::handle_broken(socket_ptr_t sock_)
{
uid_t* user = sock_->get_data<uid_t>();
if (NULL == user)
{
delete sock_;
return 0;
}

lock_guard_t lock(m_mutex);
m_clients.erase(*user);

user_logout_ret_t ret_msg;
ret_msg.uid = *user;
string json_msg = ret_msg.encode_json();
delete sock_;

map<uid_t, socket_ptr_t>::iterator it = m_clients.begin();
for (; it != m_clients.end(); ++it)
{
it->second->async_send(json_msg);
}
return 0;
}


int chat_service_t::handle_msg(const message_t& msg_, socket_ptr_t sock_)
{
try
{
m_msg_dispather.dispath(msg_.get_body() , sock_);
}
catch(exception& e)
{
sock_->async_send("msg not supported!");
logtrace((CHAT_SERVICE, "chat_service_t::handle_msg exception<%s>", e.what()));
sock_->close();
}
return 0;
}

int chat_service_t::handle(shared_ptr_t<login_req_t> req_, socket_ptr_t sock_)
{
logtrace((CHAT_SERVICE, "chat_service_t::handle login_req_t uid<%u>", req_->uid));
lock_guard_t lock(m_mutex);

pair<map<uid_t, socket_ptr_t>::iterator, bool> ret = m_clients.insert(make_pair(req_->uid, sock_));
if (false == ret.second)
{
sock_->close();
return -1;
}

uid_t* user = new uid_t(req_->uid);
sock_->set_data(user);

user_login_ret_t login_ret;
login_ret.uid = req_->uid;
string login_json = login_ret.encode_json();

online_list_ret_t online_list;

map<uid_t, socket_ptr_t>::iterator it = m_clients.begin();
for (; it != m_clients.end(); ++it)
{
online_list.uids.push_back(it->first);
it->second->async_send(login_json);
}

sock_->async_send(online_list.encode_json());
return 0;
}

int chat_service_t::handle(shared_ptr_t<chat_to_some_req_t> req_, socket_ptr_t sock_)
{
lock_guard_t lock(m_mutex);

chat_content_ret_t content_ret;
content_ret.from_uid = *sock_->get_data<uid_t>();
content_ret.content = req_->content;

string json_msg = content_ret.encode_json();
for (size_t i = 0; i < req_->dest_uids.size(); ++i)
{
m_clients[req_->dest_uids[i]]->async_send(json_msg);
}
return 0;
}

 完整代碼參見:

https://ffown.googlecode.com/svn/trunk/example/chat_server

 3. 總結

   1. 網路層使用ffown,目前還沒有socket管理模組主要是心跳功能,後續加入。

   2. 日誌直接使用printf完成,應該使用一個日誌模組完成日誌的格式化、輸出等。

   3. idl 訊息派發架構支援者json字串協議,二進位協議可以後續加入,而網路層應該具有壓縮傳輸功能

   4. 由於只是樣本程式,client端我簡單用python實現了一個。

 

 

 

 

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.