1. 程式人生 > >Teamalk在服務端新增協議

Teamalk在服務端新增協議

轉自:www.bluefoxah.org/teamtalk/add_protocol.html

1、如何增加一個協議

很多人問我如何增加一個處理協議,我先大致講下整個過程,在後面針對每一步進行詳細的講解,本次就以群裡一個群友問得如何增加修改密碼的協議為例。

因為新版TT是基於PB處理的,所以,

1、我們要在pb檔案中增加相應的命令號,協議定義。

2、重新生成協議檔案。

3、在服務端相應的地方增加邏輯處理。

4、客戶端如何處理。

下面我們就根據上面的描述來進行講解,由於時間太晚,我並沒有編譯過,是根據自己的經驗來寫的,如果你在嘗試的過程中出現錯誤,可以很容易的根據錯誤提示來排查。

2、修改PB檔案

首先我們得為修改密碼定義一個唯一的命令號,我們暫且將修改密碼的命令放到Login模組中。那我們在pb目錄下找到IM.BaseDefine.proto檔案,並開啟,在19行找到LoginCmdID的列舉定義:

  1. // command id for login

  2. enum LoginCmdID{

  3. CID_LOGIN_REQ_MSGSERVER = 0x0101;

  4. CID_LOGIN_RES_MSGSERVER = 0x0102;

  5. CID_LOGIN_REQ_USERLOGIN = 0x0103;

  6. CID_LOGIN_RES_USERLOGIN = 0x0104;

  7. CID_LOGIN_REQ_LOGINOUT = 0x0105;

  8. CID_LOGIN_RES_LOGINOUT = 0x0106;

  9. CID_LOGIN_KICK_USER = 0x0107;

  10. CID_LOGIN_REQ_DEVICETOKEN = 0x0108;

  11. CID_LOGIN_RES_DEVICETOKEN = 0x0109;

  12. CID_LOGIN_REQ_KICKPCCLIENT = 0x010a;

  13. CID_LOGIN_RES_KICKPCCLIENT = 0x010b;

  14. }

該列舉定義了各種與登入相關的命令號。我們對它進行修改,增加兩個命令號,分別表示修改密碼請求協議與修改密碼響應協議,命令號分別為:0x010c, 0x010d,修改後如下:

  1. // command id for login

  2. enum LoginCmdID{

  3. CID_LOGIN_REQ_MSGSERVER = 0x0101;

  4. CID_LOGIN_RES_MSGSERVER = 0x0102;

  5. CID_LOGIN_REQ_USERLOGIN = 0x0103;

  6. CID_LOGIN_RES_USERLOGIN = 0x0104;

  7. CID_LOGIN_REQ_LOGINOUT = 0x0105;

  8. CID_LOGIN_RES_LOGINOUT = 0x0106;

  9. CID_LOGIN_KICK_USER = 0x0107;

  10. CID_LOGIN_REQ_DEVICETOKEN = 0x0108;

  11. CID_LOGIN_RES_DEVICETOKEN = 0x0109;

  12. CID_LOGIN_REQ_KICKPCCLIENT = 0x010a;

  13. CID_LOGIN_RES_KICKPCCLIENT = 0x010b;

  14. CID_LOGIN_REQ_MODIFY_PASS = 0x010c;

  15. CID_LOGIN_RES_MODIFY_PASS = 0x010d;

  16. }

那接下來,我們我們需要考慮的是如何設定修改密碼的協議,從我個人來考慮的話,修改密碼的請求至少需要包含以下三個欄位: 1、使用者ID,該欄位用來查詢是修改哪個使用者。

2、原始密碼,這個用來驗證這個使用者是否具有修改密碼的許可權,相當於再一次做校驗。

3、新密碼,這個標誌使用者想用的新密碼。

OK,上面我們已經考慮好了修改密碼請求所需的各個欄位,那麼我們就用pb的“語法”來描述這麼一個協議,我們開啟pb/IM.Login.proto檔案,在檔案的最後增加如下程式碼:

  1. message IMModifyPassReq {

  2. //cmd id: 0x010c

  3. required uint32 user_id = 1;

  4. required string old_pass = 2;

  5. required string new_pass = 3;

  6. optional bytes attach_data = 20;

  7. }

如上所示,我們就定義好了一個修改密碼請求協議,在這裡我們做個約定,密碼是經過md5加密後的資料。

既然後修改請求,就一定要有個修改密碼響應協議,來告訴客戶端修改的結果。我們同樣需要思考一個返回協議該包含哪些資訊,經過考慮我在pb/IM.Login.proto檔案中增加如下協議:

  1. message IMModifyPassRes {

  2. //cmd id: 0x010d

  3. required uint32 user_id = 1;

  4. required uint32 status = 2;

  5. optional bytes attach_data = 20;

  6. }

user_id是請求中的user_id,status標識修改的結果是成功還是失敗。

經過以上的修改,我們協議已經定義好了。

3、重新生成協議檔案

現在我們已經修改完成了新的協議,下面就要根據pb檔案生成協議檔案程式碼了。過程比較簡單,我們上面總共修改了pb/IM.BaseDefine.proto、pb/IM.Login.proto兩個檔案,我們可以用如下命令生成協議程式碼:

  1. cd pb

  2. protoc IM.BaseDefine.proto IM.Login.proto --cpp_out=./

執行完以上命令,如果不出意外,當前目錄下就會生成四個檔案:

  1. IM.BaseDefine.pb.h

  2. IM.Login.pb.h

  3. IM.BaseDefine.pb.cc

  4. IM.Login.pb.cc

下面我們需要將新生成的程式碼檔案拷貝到我們的程式碼目錄裡面。

cp IM.BaseDefine.pb.h IM.BaseDefine.pb.cc IM.Login.pb.h IM.Login.pb.cc ../server/src/base/pb/protocol/

這樣我們將新協議程式碼已經拷貝到我們服務端程式碼目錄中了。

其實為了簡便,我們已經在pb的目錄下面已經有兩個指令碼檔案用來方便大家生成協議跟拷貝程式碼檔案,分別在pb目錄下執行以下兩個shell指令碼就行了:

  1. sh create.sh

  2. sh sync.sh

執行這兩個指令碼後就會自動生成一份最新的協議程式碼,並拷貝到服務端相應的目錄中。

4、服務端邏輯的修改

經過以上的步驟,我們已經有了修改密碼的協議了,剩下的要做的就是就是在服務端增加相應的處理邏輯。

4.1 對msg_server的修改。

客戶端直連的是msg_server,大部分時間都是與msg_server互動的。所以大部分協議都是在於msg_server互動。我們修改密碼的協議也是需要msg_server去處理的。

在msg_server的程式碼中,處理與客戶端的連結是在MsgConn.cpp中,我們首先來看下270行中得HandlePdu函式。其整體結構如下:

  1. void CMsgConn::HandlePdu(CImPdu* pPdu)

  2. {

  3. //檢查是否已登入過,或者是否是登陸包

  4. if(xxx) {

  5. throw a exception;

  6. return;

  7. }

  8. switch(pPdu->GetCommandId()) {

  9. case xxx1:

  10. _HandleXxx1(pPdu);

  11. break;

  12. case xxx2:

  13. _HandleXxx2(pPdu);

  14. break;

  15. ...

  16. default:

  17. log("");

  18. break;

  19. }

  20. }

這段邏輯主要就是對客戶端的各個協議進行處理,根據不同的協議命令來呼叫不同的邏輯處理函式。那我們增加一條處理修改密碼請求協議。

在相應的地方(根據個人習慣,選擇一個合適的地方增加,我個人習慣於將一類協議放在一塊處理),我們在294行之後,即:

  1. case CID_LOGIN_REQ_KICKPCCLIENT:

  2. _HandleKickPCClient(pPdu);

  3. break;

增加.增加後如下所示,為了排版,我省略部分程式碼,只保留了修改前後的程式碼。

  1. void CMsgConn::HandlePdu(CImPdu* pPdu)

  2. {

  3. // request authorization check

  4. if (pPdu->GetCommandId() != CID_LOGIN_REQ_USERLOGIN && !IsOpen() && IsKickOff()) {

  5. log("HandlePdu, wrong msg. ");

  6. throw CPduException(pPdu->GetServiceId(), pPdu->GetCommandId(), ERROR_CODE_WRONG_SERVICE_ID, "HandlePdu error, user not login. ");

  7. return;

  8. }

  9. switch (pPdu->GetCommandId()) {

  10. case CID_OTHER_HEARTBEAT:

  11. _HandleHeartBeat(pPdu);

  12. break;

  13. ...

  14. case CID_LOGIN_REQ_KICKPCCLIENT:

  15. _HandleKickPCClient(pPdu);

  16. break;

  17. case

  18. case CID_LOGIN_REQ_MODIFY_PASS:

  19. _HandleModifyPassRequest(pPdu);

  20. break;

  21. case CID_MSG_DATA:

  22. _HandleClientMsgData(pPdu);

  23. break;

  24. ...

  25. default:

  26. log("wrong msg, cmd id=%d, user id=%u. ", pPdu->GetCommandId(), GetUserId());

  27. break;

  28. }

  29. }

我們看到這裡呼叫了一個函式:

_HandleKickPCClient(pPdu);

這個函式就是msg_server裡面處理修改密碼邏輯的函數了,我們需要自己新增。在MsgConn.h的private中增加函式的宣告:

void _HandleModifyPassRequest(CImPdu* pPdu);

在MsgConn.cpp中,我們去實現該函式。在實現之前,我們先思考下,修改密碼中,msg_server有什麼工作需要做。msg_server在修改密碼的過程中,基本沒有什麼實質性的邏輯需要處理,所要做的,就是將該協議轉發給db_proxy_server去處理,當db_proxy_server處理完成返回之後,msg_server將處理結果返回給client,在該過程中,msg_server基本只做了一個協議轉發的作用。在這個過程中,由於msg_server與db_proxy_server之間的通訊是非同步的,當db_proxy_server處理完成返回給msg_server的時候,msg_server如何知道這個協議是需要返回給哪個客戶端會是一個問題。所以msg_server在轉發協議的時候,會將處理該客戶端連線的handle打包成CDbAttachData放在協議的最後。具體過程見程式碼:

  1. void CMsgConn::_HandleModifyPassRequest(CImPdu *pPdu)

  2. {

  3. IM::Login::IMModifyPassReq msg;

  4. CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));

  5. log("_HandleModifyPassRequest. user_id=%u.", msg.user_id());

  6. CDBServConn* pDBConn = get_db_serv_conn();

  7. if (pDBConn) {

  8. CDbAttachData attach(ATTACH_TYPE_HANDLE, m_handle, 0);

  9. msg.set_attach_data(attach.GetBuffer(), attach.GetLength());

  10. pPdu->SetPBMsg(&msg);

  11. pDBConn->SendPdu(pPdu);

  12. }

  13. }

對以上程式碼簡單的說明下. 第3行:我們定義了一個ModifyPass的協議類。 第4行:解析協議,並對協議做檢查。 第6行:獲取一個db連線 第7行:判斷連線是否為空 第8行:把與客戶端的連結控制代碼打包成CDbAttachData。 第9行:將打包好的CDbAttachData 放到pb協議中. 第10行:重新將pb協議放到CImPdu協議中。 第11行:將pdu傳送到db_proxy_server中。

至此,我們的msg_server已經處理了轉發的請求了,接下來,我們需要去db_proxy_server中做處理了。

4.2 對db_proxy_server做修改

當msg_server把請求轉發到db_proxy_server後,db_proxy_server就要做接下來的工作了。那是怎樣的一個流程呢,下面我們做個簡單的介紹。

db_proxy_server處理連結的類是CProxyConn(在檔案ProxyConn.h/ProxyConn.cpp中),在OnRead之後,如果達到了一個完整的協議包,會呼叫HandlePduBuf來對協議進行分發處理。在HandlePduBuf中,會通過s_handler_map根據命令號來呼叫GetHandler獲取處理函式。然後將相關資源封裝成一個Task,放到任務池中,供某個閒置的執行緒來呼叫。

以上簡單的描述了了db_proxy_server的處理過程,下面我們講解具體如何操作。由於修改密碼屬於使用者範疇,所以,我們接下來的主要邏輯放在UserAction.cpp及UserModel.cpp中。

首先我們需要在UserModel中增加修改密碼的邏輯,在db_proxy_server/business/UserModel.h中增加函式宣告:

uint32_t modifyUserPass(uint32_t nUserId, const string& strOldPass, const string& strNewPass);

這裡返回值我們使用的是uint32_t型別,當返回0的時候,表示修改成功,其他的值分別對應一個錯誤。 我們去db_proxy_server/business/UserModel.cpp中去實現該函式:

  1. uint32_t CUserModel::modifyUserPass(uint32_t nUserId, const string &strOldPass, const string &strNewPass)

  2. {

  3. uint32_t nRet = 0;

  4. CDBManager* pDBManager = CDBManager::getInstance();

  5. CDBConn* pDBConn = pDBManager->GetDBConn("teamtalk_master");

  6. if (pDBConn)

  7. {

  8. string strPass, strSalt;

  9. string strSql = "select password, salt from IMUser where id="+int2string(nUserId);

  10. CResultSet* pResultSet = pDBConn->ExecuteQuery(strSql.c_str());

  11. if(pResultSet)

  12. {

  13. while (pResultSet->Next())

  14. {

  15. strPass = pResultSet->GetString("password");

  16. strSalt = pResultSet->GetString("salt");

  17. }

  18. delete pResultSet;

  19. string strInPassOld = strOldPass + strSalt;

  20. char szMd5Old[33];

  21. CMd5::MD5_Calculate(strInPassOld.c_str(), strInPassOld.length(), szMd5Old);

  22. string strOutPassOld(szMd5Old);

  23. if(strOutPassOld == strPass)

  24. {

  25. string strInPass = strNewPass + strSalt;

  26. char szMd5[33];

  27. CMd5::MD5_Calculate(strInPass.c_str(), strInPass.length(), szMd5);

  28. string strOutPass(szMd5);

  29. strSql = "update IMUser set password='" + strOutPass + "' where id=" + int2string(nUserId);

  30. if(!pDBConn->ExecuteUpdate(strSql.c_str()))

  31. {

  32. nRet = -4;

  33. }

  34. }

  35. else

  36. {

  37. nRet = -3;

  38. }

  39. }

  40. else

  41. {

  42. log("no result for sql:%s.", strSql.c_str());

  43. nRet = -2;

  44. }

  45. pDBManager->RelDBConn(pDBConn);

  46. }

  47. else {

  48. log("no db connection for teamtalk_master");

  49. nRet = -1;

  50. }

  51. return nRet;

  52. }

上面我們已經實現了修改密碼的邏輯。函式中用到了md5計算,所以,我們需要包含EncDec.h標頭檔案:

#include "EncDec.h"

整個邏輯比較簡單,這裡就不再詳細贅述了。

接下來我們要在在db_proxy_server/business/UserAction.h中增加一個Action處理函式宣告:

void modifyUserPass(CImPdu* pPdu, uint32_t conn_uuid);

然後我們在db_proxy_server/business/UserAction.cpp中去實現:

  1. void modifyUserPass(CImPdu* pPdu, uint32_t conn_uuid)

  2. {

  3. IM::Login::IMModifyPassReq msg;

  4. IM::Login::IMModifyPassRes msgResp;

  5. if(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()))

  6. {

  7. CImPdu* pPduRes = new CImPdu;

  8. uint32_t nUserId = msg.user_id();

  9. string strOldPass = msg.old_pass();

  10. string strNewPass = msg.new_pass();

  11. uint32_t nRet = CUserModel::getInstance()->modifyUserPass(nUserId, strOldPass, strNewPass);

  12. log("modifyUserPass. userId=%u, result=%d.", nUserId, nRet);

  13. msgResp.set_user_id(nUserId);

  14. msgResp.set_status(nRet);

  15. msgResp.set_attach_data(msg.attach_data());

  16. pPduRes->SetPBMsg(&msgResp);

  17. pPduRes->SetSeqNum(pPdu->GetSeqNum());

  18. pPduRes->SetServiceId(IM::BaseDefine::SID_LOGIN);

  19. pPduRes->SetCommandId(IM::BaseDefine::CID_LOGIN_RES_MODIFY_PASS);

  20. CProxyConn::AddResponsePdu(conn_uuid, pPduRes);

  21. }

  22. else

  23. {

  24. log("parse pb failed");

  25. }

  26. }

好了,這樣我們就實現了修改密碼的邏輯處理,現在我們還需要最後一步,將UserAction處理修改密碼的函式註冊一下,我們到db_proxy_server/HandlerMap.cpp的Init函式中增加一行:

m_handler_map.insert(make_pair(uint32_t(CID_LOGIN_REQ_MODIFY_PASS), DB_PROXY::modifyUserPass));

4.3 再次修改msg_server

前面我們已經完成了db_proxy_server對修改密碼的邏輯處理,db_proxy_server完成修改之後,會將結果返回給msg_server。msg_server處理與db_proxy_server通訊的類是DBServerConn。

我們在msg_server/DBServerConn.h中定義一個處理返回的函式:

void _HandleModifyPassResponse(CImPdu *pPdu);

並在msg_server/DBServerConn.cpp中實現它:

  1. void CDBServConn::_HandleModifyPassResponse(CImPdu *pPdu)

  2. {

  3. IM::Login::IMModifyPassRes msg;

  4. CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));

  5. uint32_t user_id = msg.user_id();

  6. uint32_t status = msg.status();

  7. CDbAttachData attach_data((uchar_t*)msg.attach_data().c_str(), msg.attach_data().length());

  8. uint32_t handle = attach_data.GetHandle();

  9. log("_HandleModifyPassResponse, user_id=%u, status=%u.", user_id, status);

  10. CMsgConn* pMsgConn = CImUserManager::GetInstance()->GetMsgConnByHandle(user_id, handle);

  11. if (pMsgConn) {

  12. pMsgConn->SendPdu(pPdu);

  13. }

  14. }

邏輯也很簡單,這裡就不細講了,需要注意的是,這裡是從msg的attach_data中獲取到之前我們放入的handle。

我們在HandlePdu函式中的switch增加對CID_LOGIN_RES_MODIFY_PASS的處理:

  1. case CID_LOGIN_RES_MODIFY_PASS:

  2. _HandleModifyPassResponse(pPdu);

  3. break;

OK 了,所有的服務端邏輯處理完成,我們就可以編譯了。

5 客戶端的處理

由於我自己是做服務端得開發的,對客戶端的開發並不是十分了解,所以這裡只簡單提一下,不涉及介面等具體的操作,如有錯誤歡迎大家指正。

Android:可以通過pb命令產生java的協議程式碼,將對應的程式碼拷貝到Android工程中的對應目錄下即可。

iOS:由於pb官方不支援oc,所以採用的是第三方庫,有很多種實現,不同的庫生成的程式碼不同,有點麻煩,我們所使用的是:https://github.com/alexeyxo/protobuf-objc.生成完程式碼後,將程式碼拷貝到對應的目錄。

Mac: 同上。

Win:可以通過pb生成c++程式碼,具體同上。