多進程手遊流程分析

1. gate啓動時,向master註冊
2. 動態增加新的gate的流程:
   開啓新的gate時,向master註冊,master收到newgate的註冊請求後,向所有的gameserver發送PtcM2G_NewGateConnected消息
   gameserver會把此gate的IP port 名字等信息保存下來 放到gatelink的列表裏面去,重連管理器reconnmanager會嘗試對此
   newgate進行連接,此時,所有的gameserver就會和這個newgate建立連接了
3. 動態增加新的gameserver流程:
   開啓新的game時,向master註冊:RpcG2M_RegisterGs2Ms. master會處理此消息.
   master處理此消息時, 會把所有的gate信息返回給此newgame, newgame收到迴應
   後,會嘗試和這些所有的gate建立連接.
   newgame向master註冊的時機:void MSLink::OnConnectSucceed(UINT32 dwConnID), 即和master連接成功後,會開始註冊.
   那麼newgame在何處處理向master註冊的迴應呢? 在gameserver的文件:rpcg2m_registergs2ms.cpp的函數
   void RpcG2M_RegisterGs2Ms::OnReply(const RegisterGs2MsArg &roArg, const RegisterGs2MsRes &roRes, const CUserData &roUserData)
   所以不要在game中看到類似於rpcg2m_xxx的協議而感到驚訝
4. master向login註冊:
   login處理master註冊,在dblink.h中完成【在loginserver中,此處是文件名命名錯誤,dblink.h應該叫mslink.h纔對】
   在loginserver的dblink.cpp文件中,函數KKSG::ErrorCode MsLink::RegisterMs(UINT32 dwConnID, UINT32 dwZoneID, UINT32 dwServerID)
   會處理master的註冊,login會保存master的連接號 區號 服務器編號 可以參見MsLink::RegisterMs的代碼 一目瞭然
5. 玩家登陸流程【登錄服是所有服務器公用的,只有一個登錄服】
   客戶端初始化時,從資源服務器拉取服務器列表,每個服務器信息包括:大區【電信或網通】 服務器編號 服務器名字
   【此處瞭解下CDN的概念】
   此處假設選則其中的 電信--遊戲10服  這個服務器
   選中後,點確定,來確認選擇遊戲10服這個服務器,此時有個選擇gate和鏈接gate的事件發生,且有認證賬號密碼的事件發生
   輸入登錄界面的賬號名和密碼,點擊開始遊戲按鈕,會拉取到該賬號名對應的角色列表,client會和返回的QueryGateRes中的
   gate的IP和端口建立鏈接. client和gate建立鏈接後,會與login斷開鏈接.


6. client在querygateip過程中,驗證賬號名和密碼的流程:
   A. 如果登錄方式爲LOGIN_PASSWORD
 則說明不適用第三方SDK,使用賬戶名+密碼的方式登錄,此時,建立一個數據庫查詢任務DBCheckPasswordTask
      參數爲賬戶名和密碼,查詢成功之後,會確認此賬號名+密碼是否正確,如果正確,則返回,此時會調用RpcC2I_QueryGateIP::OnDelayReplyRpc【rpcc2i_querygateip.cpp】
      客戶端就會拿到gate的信息,從而與gate建立連接【獲取gate的方法爲隨機獲取,不是根據ping值或者當前gate的承載人數】
   B. 如果登陸方式爲LOGIN_SNDA_PF,則需要做token驗證【不知道此方式爲何物】
   C. 如果登陸方式爲QQ登陸或者微信登陸LOGIN_QQ_PF || LGOIN_WECHAT_PF
      也需要做TOKEN認證


7. 賬號+密碼認證和TOKEN認證的區別
   A. 賬號+密碼認證,是在querygateip過程中,到數據庫中查詢,是否存在此賬號+密碼.
   B. TOKEN認證,是走的HTTP協議,用到了rapidjson的方法. 有固定的HTTP協議參數格式,並且有私鑰,私鑰的值參見文件TXLoginTask.cpp的開始處:
const char* TXLoginTask::m_pf = "qq";
    const char* TXLoginTask::m_PlatFormSecrtKey = "authleajoy!#$^abcd1234";
http參數構建好之後,會向webthread添加一個任務,在webthread中的run函數,通過curl_multi_perform來執行這些任務.



8. login服務器中如何獲取gate列表 存在於GateIP.txt中,此文件位於exe目錄下
   DownloadGateIPTableTask
   打開gateip.txt可以看到服務器的配置.
   
9. 客戶端界面上顯示的服務器列表有三個服: 張三一服 李四一服 王五一服
10. 客戶端是如何確定服務器ID的?
    querygateip協議中,假定登錄方式爲賬號+密碼,賬號+密碼匹配後,向DB線程發送一個DBGetLastServerTask任務,查詢該賬戶上一次
登錄的服務器ID
select serverid from lastlogin where userid= account
查詢的結果即是上一次登錄的服務器ID,此是確定服務器ID的方法一
如果此賬號爲新號,數據庫中不會有此賬號上次登錄服務器的記錄,那怎麼確定該選擇哪個服務器呢?
此時會調用 GetRecommandServerID 獲取一個推薦的服務器ID
獲取推薦服務器的算法:如果某個服務器擁有推薦服務器的標示【新服 火爆 推薦 人滿等等標示】,則可以將此服務器設置爲推薦服務器
如果賬號不是新號 也沒有找到上次登錄的服務器 則也採用個和新號相同的推薦算法
11. 通過步驟10,客戶端拿到了推薦的服務器ID,還有服務器的如下信息:
roRes.set_gateip(pAddr->ip);
roRes.set_port(pAddr->port);
roRes.set_serverid(pUserInfo->serverID);
roRes.set_zonename(pInfo->zoneName);
roRes.set_servername(pInfo->serverName);
此信息爲gateserver的信息而非gameserver的信息,因爲對於客戶端來說,它只是跟gate連接,gameserver則是透明的,客戶端並不知道其存在

12. querygateip協議返回後,客戶端連接此gate,通過IP和端口,
    gateserver收到客戶端新的連接後,會向此連接推送一個消息:
clientlink.cpp: CClientLink::OnPassiveConnect-->sessionmanager::onNewConnection(uint32 dwConnID) 
在onNewConnection中,gateserver向客戶端發送消息:PtcT2C_LoginChallenge challengePtc; 且將此連接的session的狀態改爲SESSION_CHALLENGE
客戶端在收到gateserver的 PtcT2C_LoginChallenge 消息後,會向gateserver發送登錄請求:RpcC2T_ClientLoginRequest 請求裏面帶有服務器ID,
openid,token,以及平臺[ios, pc, android], 和版本號
gateserver如何處理client的登錄請求呢: 修改此session的gameserverID, 修改session的state爲SESSION_DBVERIFY
然後gateserver向master發送RpcT2M_LoginRequestNew 協議,等待master的處理,master返回後,會在RpcT2M_LoginRequestNew::OnReply 中處理登錄結果
如果master對gate向其發出的RPCT2M_LOGINRequestNew返回的結果爲成功, 即roRes.result()=ERR_SUCCESS,則修改此client的session->state=SESSION_LOGIN,
然後解除此異步過程中對該session綁定的定時器 rpct2m_loginrequestnew.cpp:56行  pSessionManager->StopLoginTimer(m_sessionID);
然後調用 DelayRepy(pSession, roRes.result(), const_cast<LoginRequestNewRes&>(roRes).mutable_accountdata()); 向 void RpcC2T_ClientLoginRequest::OnDelayReplyRpc迴應 
在 void RpcC2T_ClientLoginRequest::OnDelayReplyRpc 中,會對客戶端的回包做字段填充,填充個方法如下:
if(nErrCode == KKSG::ERR_SUCCESS)
{
KKSG::LoadAccountData* data = (KKSG::LoadAccountData*)roUserData.m_pUserPtr;
*roRes.mutable_accountdata() = *data;
}
也就是把賬戶信息返回給客戶端-->可以瞭解下KKSG::LoadAccountData的結構,如下:
<PGData>
    <Name>LoadAccountData</Name>
    <Fields>
      <PGField>
        <typeName>string</typeName>
        <variableName>account</variableName>
        <specify>REQUIRED</specify>
      </PGField>
      <PGField>
        <typeName>bytes</typeName>
        <variableName>role1</variableName>
        <specify>REQUIRED</specify>
      </PGField>
      <PGField>
        <typeName>bytes</typeName>
        <variableName>role2</variableName>
        <specify>REQUIRED</specify>
      </PGField>
      <PGField>
        <typeName>bytes</typeName>
        <variableName>role3</variableName>
        <specify>REQUIRED</specify>
      </PGField>
      <PGField>
        <typeName>bytes</typeName>
        <variableName>role4</variableName>
        <specify>REQUIRED</specify>
      </PGField>
      <PGField>
        <typeName>uint32</typeName>
        <variableName>selectSlot</variableName>
        <specify>REQUIRED</specify>
      </PGField>
    </Fields>
    </PGData>
可以得知LoadAccountData有此幾個字段:account, role1, role2, role3, role4, selectSlot.
即賬戶名 角色1 角色2 角色3  角色4 當前選擇的角色下標 

13. Master處理gate發過來的 RPCT2M_LoginRequestNew的消息的方法:
void RpcT2M_LoginRequestNew::OnCall(const LoginRequestNewArg &roArg, LoginRequestNewRes &roRes)
{
KKSG::ErrorCode nErrCode = CLoginControl::Instance()->BeginLogin(roArg.openid(), roArg.token(), m_sessionID);
if(nErrCode != KKSG::ERR_SUCCESS)
{
roRes.set_result(nErrCode);
return;
}


CLoginRequest* poRequest = CLoginControl::Instance()->GetLoginReq(roArg.openid());
if(poRequest == NULL)
{
roRes.set_result(KKSG::ERR_STATE_ERROR);
return;
}


poRequest->m_dwRpcDelayed = DelayRpc();
nErrCode = CLoginControl::Instance()->StartTokenVerify(poRequest);
if(nErrCode != KKSG::ERR_SUCCESS)
{
CUserData oUser((UINT32)nErrCode, NULL);
CRpc::ReplyDelayRpc(poRequest->m_dwRpcDelayed, oUser);
CLoginControl::Instance()->CancelLogin(m_sessionID);
return;
}
}
簡化流程就是:
KKSG::ErrorCode nErrCode = CLoginControl::Instance()->BeginLogin(roArg.openid(), roArg.token(), m_sessionID);
CLoginRequest* poRequest = CLoginControl::Instance()->GetLoginReq(roArg.openid());
nErrCode = CLoginControl::Instance()->StartTokenVerify(poRequest);
其中 StartokenVerfiy流程比較特殊,在此流程中,master會向login發送消息:RpcM2I_LoginVerfiyNew, 如下:
RpcM2I_LoginVerfiyNew* rpc = RpcM2I_LoginVerfiyNew::CreateRpc();
rpc->m_oArg.set_uid(poReq->m_qwSessionID);
rpc->m_oArg.set_logintoken(poReq->m_strToken);
rpc->m_oArg.set_serverid(MSConfig::Instance()->GetServerID());
再看看login是如何處理這個消息的:【loginserver rpcm2i_loginverifynew.cpp】
void RpcM2I_LoginVerfiyNew::OnCall(const LoginVerifyArg &roArg, LoginVerifyRes &roRes)
{
roRes.set_userid("");


UToken token;
if (!UToken::FromString(token, roArg.logintoken().data(), roArg.logintoken().size()))
{
LogWarn("db verify logintoken error: size = %d", roArg.logintoken().size());
return;
}


UINT32 serverID = LoginConfig::Instance()->GetServerID();
if (token.data[4] != serverID)
{
LogWarn("db verify send logintoken to wrong loginserver: token serverID: %d, here serverID: %d", 
token.data[4], serverID);
return;
}


char buf[32];
token.Format(buf, 32);
LogInfo("db verify logintoken [%s]", buf);


UserInfo *pUser = TokenVerifyMgr::Instance()->FindUserInfo(token);
if (pUser == NULL || pUser->isused)
{
LogWarn("db verify not found token user");
return;
}


LogInfo("db verify find token user: %s", pUser->userid.c_str());
roRes.set_userid(pUser->userid);
roRes.set_isgm(pUser->isgm);
}



















   


   
   

發佈了66 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章