天龙源码分析 - 客户端登录流程
1 登录状态定义
//登录状态 enum PLAYER_LOGIN_STATUS { LOGIN_DEBUG_SETTING, //!< — FOR DEBUG 用户参数 LOGIN_SELECT_SERVER, // 选择服务器界面. LOGIN_DISCONNECT, //!< 尚未登录 LOGIN_CONNECTING, //!< 连接服务器中… LOGIN_CONNECTED_OK, //!< 成功连接到服务器 LOGIN_CONNECT_FAILED, //!< 连接到服务器失败 LOGIN_ACCOUNT_BEGIN_REQUESTING, // 发送密码之前状态 LOGIN_ACCOUNT_REQUESTING, //!< 发送帐号信息数据包到服务器… LOGIN_ACCOUNT_OK, //!< 帐号验证成功 LOGIN_ACCOUNT_FAILED, //!< 帐号验证失败 LOGIN_WAIT_FOR_LOGIN, // 排队进入游戏状态. LOGIN_FIRST_LOGIN, // 首次登录 LOGIN_CHANGE_SCENE, // 切换场景的重登录 };
2 登录流程采用轮回方式,在Tick中判断当前所处状态
VOID CGamePro_Login::Tick(VOID) { CGameProcedure::Tick(); switch(m_Status) { case LOGIN_DEBUG_SETTING: { if(!CGameProcedure::s_pUISystem) { SetStatus(CGamePro_Login::LOGIN_DISCONNECT); } else { //DO NOTING,WAIT UI… } } break; case LOGIN_SELECT_SERVER:// 选择服务器状态 { //— for debug if(CGameProcedure::s_pVariableSystem->GetAs_Int(“GameServer_ConnectDirect”) == 1) { //直接切换到Change-Server流程 CGameProcedure::SetActiveProc((CGameProcedure*)CGameProcedure::s_pProcChangeScene); return; } //— for debug break; } case LOGIN_DISCONNECT: { s_pGfxSystem->PushDebugString(“Connect to login server %s:%d…”, m_szLoginServerAddr, m_nLoginServerPort); //开始登录 SetStatus(LOGIN_CONNECTING); CNetManager::GetMe()->ConnectToServer(m_szLoginServerAddr, m_nLoginServerPort); } break; case LOGIN_CONNECTING: break; //连接成功 case LOGIN_CONNECTED_OK: { // 设置正在验证密码 // SetStatus(LOGIN_ACCOUNT_REQUESTING); } break; //连接失败 case LOGIN_CONNECT_FAILED: CNetManager::GetMe()->Close(); SetStatus(LOGIN_SELECT_SERVER); break; // 正在验证用户名和密码. case LOGIN_ACCOUNT_REQUESTING: { // 判断是否超时, 超时就提示错误信息. break; } case LOGIN_ACCOUNT_BEGIN_REQUESTING: { break; } //登录信息验证成功 case LOGIN_ACCOUNT_OK: { // 保存选择的服务器 CGameProcedure::s_pVariableSystem->SetAs_Int(“Login_Area”, CGameProcedure::s_pProcLogIn->m_iCurSelAreaId, FALSE); CGameProcedure::s_pVariableSystem->SetAs_Int(“Login_Server”, CGameProcedure::s_pProcLogIn->m_iCurSelLoginServerId, FALSE); if(m_bReLogin) { //直接进入场景 CGameProcedure::s_pProcEnter->SetStatus(CGamePro_Enter::ENTERSCENE_READY); CGameProcedure::s_pProcEnter->SetEnterType(ENTER_TYPE_FIRST); CGameProcedure::SetActiveProc((CGameProcedure*)CGameProcedure::s_pProcEnter); } else { // 设置登录状态为首次登录(以区分切换场景的登录状态) CGameProcedure::s_pProcLogIn->FirstLogin(); //转入人物选择循环 CGameProcedure::SetActiveProc((CGameProcedure*)s_pProcCharSel); } } break; default: break; } }
游戏初始化时,为LOGIN_SELECT_SERVER状态。
2 然后,如果用户填了用户名和密码,点击登录,则触发下面事件
// 连接到login server int CGamePro_Login::ConnectToLoginServer(int iAreaIndex, int iLoginServerIndex) { if( iAreaIndex < 0 || iAreaIndex >= m_iAreaCount ) return 1; if( iLoginServerIndex >= (int)m_pAreaInfo[iAreaIndex].LoginInfo.size() ) return 1; // 设置ip地址和端口号. SetIpAddr( m_pAreaInfo[iAreaIndex].LoginInfo[iLoginServerIndex].szIp.c_str() ); SetPort( m_pAreaInfo[iAreaIndex].LoginInfo[iLoginServerIndex].iPort ); // 设置当前的状态为非连接状态 SetStatus(LOGIN_DISCONNECT); // 通知界面显示系统提示信息, 正在连接服务器. CGameProcedure::s_pEventSystem->PushEvent( GE_GAMELOGIN_SHOW_SYSTEM_INFO_NO_BUTTON, “正在连接到服务器…..”); return 0; }
把游戏登录状态设置为LOGIN_DISCONNECT
3 步骤1中,如果检测到状态为LOGIN_DISCONNECT,则触发Net事件
case LOGIN_DISCONNECT: { s_pGfxSystem->PushDebugString(“Connect to login server %s:%d…”, m_szLoginServerAddr, m_nLoginServerPort); //开始登录 SetStatus(LOGIN_CONNECTING); CNetManager::GetMe()->ConnectToServer(m_szLoginServerAddr, m_nLoginServerPort); } break; ConnectToServer具体执行代码如下: //创建登录线程 UINT nThreadID; m_hConnectThread = (HANDLE)::_beginthreadex(NULL, 0, _ConnectThread, this, CREATE_SUSPENDED|THREAD_QUERY_INFORMATION, &nThreadID ); if (m_hConnectThread == NULL) { TDThrow(_T(“(CNetManager::ConnectToServer)Can’t create connect thread!”)); } 在这里,创建一个登录线程,用于执行连接服务器,具体执行为_ConnectThread的回调函数: //连接线程返回值 // 0 : 尚未连接 // 1 : 成功连接到服务器 // -1 : 创建SOCKET发生错误 // -2 : 无法连接到目的服务器 // -3 : 超时错误 INT CNetManager::ConnectThread(VOID) { //关闭SOCKET m_Socket.close(); //创建新的SOCKET if(!m_Socket.create()) { return -1; } //连接到服务器 if(!m_Socket.connect( m_strServerAddr.c_str(), m_nServerPort )) { m_Socket.close(); return -2 ; } //成功连接 return 1; }
执行上面代码后,客户端等服务端返回了,监视是否连接成功,是在Net的Tick里面采用轮回查询方式
// Tick 游戏登录流程. VOID CNetManager::TickGameLoginProcedure() { switch(CGameProcedure::s_pProcLogIn->GetStatus()) { case CGamePro_Login::LOGIN_DEBUG_SETTING: { break; } case CGamePro_Login::LOGIN_SELECT_SERVER: { break; } //尚未登录,准备状态 case CGamePro_Login::LOGIN_DISCONNECT: { break; } //SOCKET连接中… case CGamePro_Login::LOGIN_CONNECTING: { WaitConnecting(); break; } WaitConnecting()的代码如下: VOID CNetManager::WaitConnecting(VOID) { //监测登录线程是否结束 int nExitCode = 0; if(::GetExitCodeThread(m_hConnectThread, (DWORD*)&nExitCode)) { } //登录线程未结束 if( STILL_ACTIVE == nExitCode) { //检查是否超时 UINT dwTimeNow = CGameProcedure::s_pTimeSystem->GetTimeNow(); UINT dwUsed = CGameProcedure::s_pTimeSystem->CalSubTime(m_timeConnectBegin, dwTimeNow); //超时 if(dwUsed >= MAX_CONNECT_TIME) { //强制结束登录线程 TerminateThread(m_hConnectThread, 0); nExitCode = -3; } //继续等待 else { return; } } //登录线程已经结束 关闭句柄 if(CloseHandle(m_hConnectThread)) { m_hConnectThread = NULL; } //登录过程中发生错误 if(nExitCode < 0) { //LPCTSTR szErrorDesc; switch(nExitCode) { case -1: { SetNetStatus(CONNECT_FAILED_CREATE_SOCKET_ERROR); CGameProcedure::s_pEventSystem->PushEvent(GE_NET_CLOSE, “创建网络连接失败!”); break; } case -2: { SetNetStatus(CONNECT_FAILED_CONNECT_ERROR); CGameProcedure::s_pEventSystem->PushEvent(GE_NET_CLOSE, “目的服务器可能关闭!”); break; } case -3: { SetNetStatus(CONNECT_FAILED_TIME_OUT); CGameProcedure::s_pEventSystem->PushEvent(GE_NET_CLOSE, “连接超时!”); break; } default: { SetNetStatus(CONNECT_FAILED_CONNECT_ERROR); CGameProcedure::s_pEventSystem->PushEvent(GE_NET_CLOSE, “未知错误!”); break; } } this->Close(); return; } //连接成功后设置为非阻塞模式和设置Linger参数 if(!m_Socket.setNonBlocking() || !m_Socket.setLinger(0)) { SetNetStatus(CONNECT_FAILED_CONNECT_ERROR); TDThrow(_T(“(CNetManager::Tick)SetSocket Error”)); return; } //通知登录流程,SOCKET连接成功 if(CGameProcedure::GetActiveProcedure() == (CGameProcedure*)CGameProcedure::s_pProcLogIn) { CGameProcedure::s_pProcLogIn->SendClConnectMsg(); SetNetStatus(CONNECT_SUCESS);// } else if(CGameProcedure::GetActiveProcedure() == (CGameProcedure*)CGameProcedure::s_pProcChangeScene) { SetNetStatus(CONNECT_SUCESS);// } return; }
代码很清晰,先判断登录线程的返回结果。如果登录线程没结束,退出循环;如果线程已经退出,则检查退出状态,登录成功,则设置登录状态为CONNECT_SUCESS
5 不过,从登录界面切换到人物选择界面,判断登录状态是否为LOGIN_ACCOUNT_OK。但客户端未查到更新到这个状态的地方,猜测是服务器更新这个状态的
//登录信息验证成功 case LOGIN_ACCOUNT_OK: { // 保存选择的服务器 CGameProcedure::s_pVariableSystem->SetAs_Int(“Login_Area”, CGameProcedure::s_pProcLogIn->m_iCurSelAreaId, FALSE); CGameProcedure::s_pVariableSystem->SetAs_Int(“Login_Server”, CGameProcedure::s_pProcLogIn->m_iCurSelLoginServerId, FALSE); if(m_bReLogin) { //直接进入场景 CGameProcedure::s_pProcEnter->SetStatus(CGamePro_Enter::ENTERSCENE_READY); CGameProcedure::s_pProcEnter->SetEnterType(ENTER_TYPE_FIRST); CGameProcedure::SetActiveProc((CGameProcedure*)CGameProcedure::s_pProcEnter); } else { // 设置登录状态为首次登录(以区分切换场景的登录状态) CGameProcedure::s_pProcLogIn->FirstLogin(); //转入人物选择循环 CGameProcedure::SetActiveProc((CGameProcedure*)s_pProcCharSel); } } break;
这样就进入角色选择界面了
(另外一种可能进入角色选择界面的方式见《选择角色流程分析》)