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, // 切换场景的重登录
};
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 ;
}
}
{
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 ;
}
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 ;
{
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! " ));
}
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 ;
}
// 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 ;
}
4 执行上面代码后,客户端等服务端返回了,监视是否连接成功,是在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 ;
}
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 ;
}
{
// 监测登录线程是否结束
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 ;
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 ;
这样就进入角色选择界面了
(另外一种可能进入角色选择界面的方式见《选择角色流程分析》)