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

摘要:
˂未能连接到服务器LOGIN_ACCOUNT_BEGIN_REQUEESTING,//发送密码前的状态LOGIN _ ACCOUNT_REQUESTING,//!PushDebugString(“连接到登录服务器%s:%d…”

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;

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

免责声明:文章转载自《天龙源码分析 客户端登录流程》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇软件测试需求分析与跟踪利用geo3d地图数据画地图上面的柱子下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

rsync + inotify 实现主机间数据实时同步的原理

一、rsync实现两台服务器数据同步的原理:        Rsync 是基于rsync算法校验源(SRC)与目标(DEST)之间的差异实现数据同步的。也就是说,当使用rsync工具同步数据时候,只复制源(SRC)发生改变的文件,到目标(DEST),这类似于增量备份。所以rsync同步数据的速度是很快的。但是rsync的传输性能有点差,在海量小文件需要同步...

Linux源码编译安装和卸载

Linux下正常的编译安装/卸载 源码的安装一般由3个步骤组成: 配置(configure) 编译(make) 安装(make install)。 configure文件是一个可执行的脚本文件,它有很多选项,在待安装的源码目录下使用命令./configure –help可以输出详细的选项列表。 其中--prefix选项是配置安装目录,如果不配置该选项,...

Linux下Nodejs安装(完整详细)

node下载地址: http://nodejs.cn/download/ 首先去官网下载代码,这里一定要注意安装分两种,一种是Source Code源码,一种是编译后的文件。我就是按照网上源码的安装方式去操作编译后的文件,结果坑了好久好久。   (一) 编译好的文件     简单说就是解压后,在bin文件夹中已经存在node以及npm,如果你进入到对应文...

负载均衡介绍

四层和七层负载均衡的区别 (一)简单理解四层和七层负载均衡: ① 七层就是基于URL或主机名等应用层信息的负载均衡,接收请求,然后再分配到真实的服务器; 四层就是基于IP+端口的负载均衡,接收请求,然后再分配到真实的服务器; 三层负载均衡会通过一个虚拟IP地址接收请求,然后再分配到真实的IP地址; 二层负载均衡会通过一个虚拟MAC地址接收请求,然后再分配到真...

使用Wireshark查看HTTPS中TLS握手过程

通过使用Wireshark抓包分析TLS握手的过程,可以更容易理解和验证TLS协议,本文将先介绍Wireshark解密HTTPS流量的方法,然后分别验证TLS握手过程和TLS会话恢复的过程。 一、使用Wireshark解密HTTPS流量的方法 TLS对传输数据进行了加密,直接使用Wireshark查看,TLS协议之上的协议细节(应用层 HTTP)完全看不到...

Spring源码阅读笔记01:源码阅读环境准备

1. 写在前面 对于做Java开发的同学来说,Spring就像是一条绕不过去的路,但是大多数也只是停留在对Spring的简单使用层面上,对于其背后的原理所知不多也不愿深究,关于这个问题,我在平时的生活及工作中也在不断思考。 为什么要阅读Spring的源码? 不是为了读源码而读源码,Spring有很好的设计思想,值得学习; Spring是当前使用最广泛的一...