天龙源码分析 - 客户端登录流程

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;

 

这样就进入角色选择界面了
(另外一种可能进入角色选择界面的方式见《选择角色流程分析》)

posted on 2012-08-21 13:03  DieAngel  阅读(566)  评论(0编辑  收藏  举报

导航