<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] 參考鏈接:https://blog.csdn.net/sky1work2?t=1 ## 服務器架構簡圖如下: ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xNzcxNDc1NC1mZjA4MTg5ZTlmYWU2ZmMxLmpwZw) ## 連線說明: * **實線**:表示客戶端登錄流程,①②③④⑤表示登錄流程,詳細解釋見下文。 * **虛線**:表示服務器間的連接,虛線箭頭指向監聽方。 ## 架構說明: | 英文名稱 | 簡稱 | 中文名稱 | 功能簡介 | 單個大區需求數量 | 數量說明 | | --- | --- | --- | --- | --- | --- | | **GameClient** | GC | 游戲客戶端 | 不解釋 | n | 不超服務器承載上限均可 | | **LoginSer** | LS | 游戲登錄服務器 | 用于登錄驗證的服務器 | 1 | 登錄入口僅需一個 | | **GateSer** | GS | 游戲網關服務器 | 用于客戶端與其場景服和中心服通訊的轉接 | n | 根據客戶端連接數量調整 | | **BalanceSer** | BS | 游戲負載均衡服務器 | 用于負載均衡,給客戶端分配合理的網關服,其上連接多臺GS | n | 根據實際需求配置 | | **SceneSer** | SS | 游戲場景服務器 | 用于游戲戰斗的服務器 | n | 根據實際需求配置 | | **CentralSer** | CS | 游戲中心服務器 | 用于玩家數據管理,匹配等核心功能服務器 | 1 | 統一管理玩家數據 | | **LogSer** | | 游戲日志服務器 | 用于游戲日志收集 | 1 | 統一日志收集 | | **UserRedis** | | 游戲玩家數據緩存服務器 | 用于緩存玩家數據 | 1 | 統一數據緩存 | | **LogicRedis** | | 游戲邏輯數據緩存服務器 | 用于緩存游戲數據 | 1 | 統一數據緩存 | | **MySql** | | 游戲數據庫 | 用于持久化游戲及玩家數據 | 1 | 統一數據存儲 | | **Remote** | | 游戲遠程控制端 | 用于輸入gm命令等遠程控制 | n | 根據實際需求配置 | ## 登錄流程: * **①②**:客戶端發送消息AskLogin到LS,LS根據客戶端登錄類型生成相應的url,通過消息隊列*m\_SDKCallbackQueue*發送給子線程調用url來獲取登錄驗證結果,子線程將url返回結果通過消息隊列*m\_DBCallbackQueue*發送給主線程,根據返回結果將BS列表發送給客戶端。**ps:這塊需要了解boost庫和liburl庫的基本用法** * **③④**:客戶端收到LS返回的BS服務器列表后,選擇其中一臺BS發送消息 *eMsgToBSFromGC_OneClientLogin* ,BS收到該消息后會向LS發消息 *eMsgToLSFromBC_OneClinetLoginCheck* 來進行身份驗證,驗證通過后會返回給 *BS一個eMsgToBSFromLS_OneClinetLoginCheckRet* 消息, BS給該玩家生成一個唯一token并將玩家token信息根據負載均衡策略發送 *eMsgToGSFromBS_OneUserLoginToken*消息 給目前負載較小的GS來進行登記,登記成功后GS返回其ip和端口等信息 發送 *eMsgToBSFromGS_OneUserLoginTokenRet* 到BS,進而發送給客戶端。 * **⑤**:客戶端收到GS的ip和端口等信息后,發送登錄消息給GS,GS校驗token成功后會將登錄信息轉發給CS,游戲玩家數據實際是在CS上加載并創建玩家實體。到此,玩家登錄完成,后續玩家在CS和SS上的操作消息都會通過GS作為中間橋梁和客戶端通訊。 ## 登錄服務代碼解析 監聽端口:49997 供BS服務連接 監聽端口:49996 供客戶端連接 ## CIocpCtrl:IOCP控制類 多線程循環查詢IOCP內部的網絡事件,并分派處理.這里多個工作線程僅僅是將所有的網絡事件放入循環 內部將創建2 * CPU個工作線程(Worker Item). 多線程調用函數 * 新建客戶端連接 poListener->OnAccept(bRet, pstPerIoData); * 接受消息 poSock->OnRecv(dwByteTrabsferred); * 發送消息 poSock->OnSend(dwByteTrabsferred); ``` void OnExecute() { CCPSock* poSock; //代表一個套接字類 CCpListener* poListener; //IOCP監聽器 ... //當有客戶端請求創建連接時 if(pstPerHandleData->bListen) { poListener->OnAccept(bRet, pstPerIoData); } else { poSock = (CCPSock*)pstPerHandleData->ptr; ··· switch(pstPerIoData->nOp) { case IOCP_RECV: //接受數據 { poSock->DecPostRecv(); if (dwByteTrabsferred > 0) { poSock->OnRecv(dwByteTrabsferred); } else { INFO(_SDT("[%s:%d]CCPSock connID=%d error %d, close it, socket :%d "), MSG_MARK, poSock->GetConnectionID(), ::WSAGetLastError(), poSock->GetSock()); poSock->OnClose(); } } break; case IOCP_SEND: //發送數據 { poSock->DecPostSend(); if (dwByteTrabsferred > 0) { poSock->OnSend(dwByteTrabsferred); } else { INFO(_SDT("[%s:%d]CCPSock connID=%d error %d, close it"), MSG_MARK, poSock->GetConnectionID(), ::WSAGetLastError()); poSock->OnClose(); } } break; case IOCP_CLOSE: { poSock->OnClose(false); } break; default: ; } } ... } ``` ## CCpListener: IOCP監聽器 IOCP控制類在有新客戶端連接上來時調用監聽器的OnAccept函數 ``` void CCpListener::OnAccept(BOOL bSucc, SPerIoData* pstPerIoData) { ... CUCConnection* poConnection = & pConnData->connection; //創建連接對象 ISDSession* poSession = m_poSessionFactory->CreateSession(poConnection); poConnection->SetSession(poSession); ... // // 應該先投遞連接事件再關聯套接口,否則可能出現第一個Recv事件先于連接事件入隊列 // //CEventMgr::Instance()->PushEstablishEvt(poConnection, true, m_dwID); if(false == poSock->AssociateWithIocp()) { poSock->DoClose(); } else { //從內核中拷貝數據到內存中 if(false == poSock->PostRecv()) { poSock->DoClose(); } } } ``` ## CCPSock:套接字類 在CCPSocket::PostRecv();接收數據到內存, 并且接受數據計數+1 ``` bool CCPSocket::PostIRecv() { ... if (0 != WSARecv(m_hSock, &m_stRecvIoData.stWsaBuf, 1, &dwReadLen, &dwFlags, &m_stRecvIoData.stOverlapped, NULL)) { int errNo = WSAGetLastError(); if (errNo != WSA_IO_PENDING) { WARN(_SDT("[%s:%d]post WSARecv failed, errNo=%d, %p "), MSG_MARK, errNo, m_pRecvBuf); return false; } } IncPostRecv(); // 接受消息計數+1 ... } ``` CCPSock::OnRecv(DWORD dwBytes) 添加一個完整的數據包到接收數據事件中 ``` void CCPSock::OnRecv(DWORD dwBytes) { ... CEventMgr::Instance()->PushRecvEvt(m_pConnData, GetConnectionID(), p, nUsed); ... } ``` ## INetSessionMgr:網絡會話管理類 主線程定時調用 INetSessionMgr::GetInstance()->Update(); ``` void INetSessionMgr::Update() { mNetModule->Run(); ... } ``` ## IUCNet* mNetModule : 網絡模塊 獲取 NETEVT_RECV 接收數據事件 ``` bool CUCODENetWin::Run(INT32 nCount) { ... switch(stEvent.nType) { case NETEVT_RECV: _ProcRecvEvt(&stEvent.stUn.stRecv); break; ... } CEventMgr::Instance()->ReleaseNetEvt(pstEvent); } ``` ``` void CUCODENetWin::_ProcRecvEvt(SRecvEvt* pstEvent) { ... pConnData->connection.OnRecv(m_pRecvBuf, pstEvent->nLen); ... } ``` ## CClientSession : 客戶端會話類 客戶端連接類 CClientSession 收到數據回調 ``` void CUCConnection::OnRecv(const char* pData, INT32 nLen) { if(m_nConnStat != CONN_ASSOCIATE) { return; } SDASSERT(m_poSession != NULL); m_poSession->OnRecv(pData, nLen); //回調 } ``` > ISDSession*m_poSession 基類指針指向子類 CClientSession* 回調到 類 INetSession中,因為 CClientSession 是 INetSession子類 ``` void UCAPI INetSession::OnRecv(const char* pBuf, UINT32 dwLen){ ··· bool bRet = pNode->mHandle(pMsgData, n32MsgLen, this, pNetHeader->type); ··· } ``` 在類 CClientSession 中 收到第1消息:請求登錄,放入登錄隊列 ``` bool CClientSession::Msg_Handle_Init(const char* pMsg, int n32MsgLength, INetSession* vthis, int n32MsgID) { // 收到第1消息:請求登錄,放入登錄隊列 boost::shared_ptr<GCToLS::AskLogin> sLogin = ParseProtoMsg<GCToLS::AskLogin>(pMsg, n32MsgLength); if (!sLogin){ //ELOG(LOG_ERROR, "Login Fail With Msg Analysis Error."); SDKAsynHandler::GetInstance().PostToLoginFailQueue(eEC_TBInvalidToken, vthis->GetID()); return 0; } SDKAsynHandler::GetInstance().CheckLogin(*sLogin, vthis->GetID()); vthis->SetInited(true,true); return true; } ``` ``` int SDKAsynHandler::CheckLogin(GCToLS::AskLogin& sAskLogin, int gcnetid){ ... switch(un32platform) { case ePlatform_PC: sSendData = "PCTest"; break; } // 生產消息,往隊列 m_SDKCallbackQueue 里加數據 PostMsg(sSendData.c_str(), sSendData.size(), sAskLogin.msgid(), gcnetid, (EUserPlatform)un32platform); } ``` ## 生產消費者模型1 通過 PostMsg()函數生產消息,往隊列 m_SDKCallbackQueue 里加數據 ``` void PostMsg(const char* pMsg, int length, int msgid, int gcnetid, EUserPlatform eplat); ``` 在一個線程里,啟動一個定時器 newtimer_cb,消費隊列 m_SDKCallbackQueue ## 生產消費者模型2 SendToInsertData()函數生產消息 推送到隊列 m_DBCallbackQueue ``` SdkConnector::GetInstance().SendToInsertData(sUserData.uin, sTempInfo, gcnetid); ``` 主線程,循環消費 m_DBCallbackQueue ``` SdkConnector::GetInstance().Update(); ``` 登錄成功, 給客戶端推送BS服務器列表 ``` void SdkConnector::Update(){ ... PostMsgToGC_NotifyServerList(gcnetid); ... } ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看