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

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 ;
}

 

 

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 ;
            }

 

 

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 ;

 

 

这样就进入角色选择界面了

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值