前言:
v0.0.1 2015-04-10 誉小痕(shawhen2012@https://www.wendangku.net/doc/c814208098.html,)
v0.0.2 2015-04-12 誉小痕(shawhen2012@https://www.wendangku.net/doc/c814208098.html,)
v0.0.3 2015-04-23 誉小痕(shawhen2012@https://www.wendangku.net/doc/c814208098.html,)
changelog请查看:https://www.wendangku.net/doc/c814208098.html,/forum.php?mod=viewthread&tid=247&extra=page%3D1
新增内容以**New in version x.x.x**标示
友情提醒:打开Word的导航标题查看,因为我不会在文档内对标题进行缩进
基于kbengine 0.4.20 (兼容0.5.0)
(现在和这份文档一起的有一个kbengine主干活动图,建议先看看那个图,参照图中的流程然后对照本文档理解流程的实现细节。活动图可在changelog帖子中找到下载地址)
MMOG服务端是一种高品质的工程项目,品读开源的kbe是一种乐趣。本文档我带童鞋们一起领略一下。囿于我知识面和经验方面所限,文中所述之处难免有错误存在,还请读童鞋们睁大慧眼,如果你发现错误,可以电邮至shawhen2012@https://www.wendangku.net/doc/c814208098.html,。(因为我个人懒散或者时间仓促的关系,这个文档的排版有点小乱。。。)
其他牛逼哄哄的前言就不说了。
从理论上来讲,我们阅读一份源代码,首先应该基于现有的文档从整体上把握项目的架构之后再庖丁解牛一般地细分阅读,不过在我写这个文档的现在,我暂时没发现这样的文档,所以我就按照我自己的阅读顺序从而编排这个文档的内容。
**NEW in version 0.0.3**
客户端概要:
在过去0.0.2中我们对kbengine的服务端进行了大致流程的了解,接下来为了更加全面地了解到kbengine的整个框架流程(从理论上来讲不解读客户端我们也能完完整整地解读完服务端,不过我们还是看一看,以免管窥蠡测之嫌)。本文档采用Ogre的Demo,主干活动图现已更新到v0.0.2,包含ogre demo的活动图。
你可能担心自己不会Ogre,会看不懂这个文档,其实不用太担心,我也不会,我之所以选Ogre,是因为Unity,cocos2d-x,Ogre这些我一个都不会;-( ,但Ogre的demo对于我们解读kbengine来说最clean,所以我选了这个来分析。
1.main函数:
a)初始化kbengine引擎:
找到客户端的main函数(app/OgreApplication.cpp),可以看到主要就是装载了一下kbengine.dll,然后执行了kbe_init,我们跟进kbe_init(kbengine_dll/main.cpp):
bool kbe_init()
{
g_componentID = genUUID64();
g_componentType = CLIENT_TYPE;
//pserverconfig = new ServerConfig;
pconfig = new Config;
if(!loadConfig())
{
ERROR_MSG("loadConfig() is error!\n");
return false;
}
DebugHelper::initialize(g_componentType);
INFO_MSG( "-----------------------------------------------------------------------------------------\n\n\n");
#ifdef USE_OPENSSL
if(KBEngine::KBEKey::getSingletonPtr() == NULL)
KBEngine::KBEKey kbekey(KBEngine::Resmgr::getSingleton().matchPath("key/") + "kbengine_public.key", "");
#endif
if(g_pScript == NULL)
g_pScript = new KBEngine::script::Script();
if(g_pDispatcher == NULL)
g_pDispatcher = new Network::EventDispatcher();
if(g_pNetworkInterface == NULL)
{
g_pNetworkInterface = new Network::NetworkInterface(g_pDispatcher,
0, 0, "", 0, 0,
0, "", 0, 0);
}
if(!installPyScript(*g_pScript, g_componentType))
{
ERROR_MSG("installPyScript() is error!\n");
return false;
}
g_pApp = new ClientApp(*g_pDispatcher, *g_pNetworkInterface, g_componentType, g_componentID);
g_pApp->setScript(g_pScript);
START_MSG(COMPONENT_NAME_EX(g_componentType), g_componentID);
if(!g_pApp->initialize())
{
ERROR_MSG("app::initialize is error!\n");
g_pApp->finalise();
Py_DECREF(g_pApp);
g_pApp = NULL;
uninstallPyScript(*g_pScript);
SAFE_RELEASE(g_pNetworkInterface);
SAFE_RELEASE(g_pScript);
SAFE_RELEASE(g_pDispatcher);
SAFE_RELEASE(pserverconfig);
SAFE_RELEASE(pconfig);
return false;
}
if(g_pTelnetServer == NULL)
{
g_pTelnetServer = new TelnetServer(g_pDispatcher, g_pNetworkInterface);
g_pTelnetServer->pScript(g_pScript);
if(!g_pTelnetServer->start(pconfig->telnet_passwd,
pconfig->telnet_deflayer,
pconfig->telnet_port))
{
ERROR_MSG("app::initialize: TelnetServer is error!\n");
return false;
}
}
else
{
g_pTelnetServer->pScript(g_pScript);
}
if(g_pThreadPool == NULL)
{
g_pThreadPool = new thread::ThreadPool();
if(!g_pThreadPool->isInitialize())
{
g_pThreadPool->createThreadPool(1, 1, 4);
}
else
{
ERROR_MSG("g_threadPool.isInitialize() is error!\n");
return false;
}
}
INFO_MSG(fmt::format("---- {} is running ----\n", COMPONENT_NAME_EX(g_componentType)));
PyEval_ReleaseThread(PyThreadState_Get());
KBEConcurrency::setMainThreadIdleCallbacks(&releaseLock, &acquireLock);
g_pThreadPool->addTask(new KBEMainTask());
return true;
}
有没有感觉很熟悉?是的,跟v0.0.2中分析得到的服务端初始化流程大同小异,我们从installPyScript开始看。
1)脚本引擎安装(暂未完成):
要读懂kbe的脚本引擎模块需要对python这种脚本语言有所了解,特别是python嵌入c的各种api,如果你还没准备好建议你去这里过一遍:python3 c api手册。
看看installPyScript的代码(client_lib/kbemain.h):
inline bool installPyScript(KBEngine::script::Script& script, COMPONENT_TYPE componentType)
{
if(Resmgr::getSingleton().respaths().size() <= 0 ||
Resmgr::getSingleton().getPyUserResPath().size() == 0 ||
Resmgr::getSingleton().getPySysResPath().size() == 0 ||
Resmgr::getSingleton().getPyUserScriptsPath().size() == 0)
{
ERROR_MSG("EntityApp::installPyScript: KBE_RES_PATH is error!\n");
return false;
}
std::wstring user_scripts_path = L"";
wchar_t* tbuf =
KBEngine::strutil::char2wchar(const_cast
if(tbuf != NULL)
{
user_scripts_path += tbuf;
free(tbuf);
}
else
{
ERROR_MSG("EntityApp::installPyScript: KBE_RES_PATH error[char2wchar]!\n");
return false;
}
std::wstring pyPaths = user_scripts_path + L"common;";
pyPaths += user_scripts_path + L"data;";
pyPaths += user_scripts_path + L"user_type;";
if(componentType == CLIENT_TYPE)
pyPaths += user_scripts_path + L"client;";
else
pyPaths += user_scripts_path + L"bots;";
std::string kbe_res_path = Resmgr::getSingleton().getPySysResPath();
kbe_res_path += "scripts/common";
tbuf = KBEngine::strutil::char2wchar(const_cast
bool ret = script.install(tbuf, pyPaths, "KBEngine", componentType);
free(tbuf);
EntityDef::installScript(script.getModule());
client::Entity::installScript(script.getModule());
Entities
EntityGarbages
return ret;
}
基本流程可以理解为先调用Script(pyscript/script.h/.cpp)的install接口,我们跟进看一下:设置python解释器的“家”路径->初始化->获取__main__模块->添加一个叫”KBEngine”的模块(到sys.modules)->为KBEngine模块添加一个叫做component(值为安装脚本引擎的组件的类型字符串,比如loginapp/client……)的变量->为”KBEngine”模块添加一个叫做genUUID64的方法->安装几个dll模块(socket/hashlib/select/ctypes/elementtree/unicodedata/pyexpat)->初始化”KBEngine”模块->将KBEngine模块对象添加为__main__模块的属性->安装”Math”模块->安装“KBExtra”模块。
2)ClientApp,计时器添加:
我们跟进app的initialize接口,在initializeBegin中看到(lib/client_lib/clientapp.cpp):
bool ClientApp::initializeBegin()
{
gameTimer_= this->dispatcher().addTimer(1000000 / g_kbeConfig.gameUpdateHertz(), this,
reinterpret_cast
ProfileVal::setWarningPeriod(stampsPerSecond() / g_kbeConfig.gameUpdateHertz());
Network::g_extReceiveWindowBytesOverflow = 0;
Network::g_intReceiveWindowBytesOverflow = 0;
Network::g_intReceiveWindowMessagesOverflow = 0;
Network::g_extReceiveWindowMessagesOverflow = 0;
Network::g_receiveWindowMessagesOverflowCritical = 0;
return true;
}
即添加了一个计时器到事件派发器。由v0.0.2中得出的结论我们知道,计时器是在事件派发器的processTimers中处理的,所以我们留下了一个问题,事件派发器是在哪进行事件分发的。
我们再看看计时器的处理,如下(lib/client_lib/clientapp.cpp):
void ClientApp::handleTimeout(TimerHandle, void * arg)
{
switch (reinterpret_cast
{
case TIMEOUT_GAME_TICK:
{
handleGameTick();
}
default:
break;
}
}
我们跟到handleGameTick:
void ClientApp::handleGameTick()
{
g_kbetime++;
threadPool_.onMainThreadTick();
networkInterface().processChannels(KBEngine::Network::MessageHandlers::pMainMessageHa ndlers);
tickSend();
switch(state_)
{
case C_STATE_INIT:
state_ = C_STATE_PLAY;
break;
case C_STATE_INITLOGINAPP_CHANNEL:
state_ = C_STATE_PLAY;
break;
case C_STATE_LOGIN:
state_ = C_STATE_PLAY;
if(!ClientObjectBase::login())
{
WARNING_MSG("ClientApp::handleGameTick: login is failed!\n");
return;
}
break;
case C_STATE_LOGIN_GATEWAY_CHANNEL:
{
state_ = C_STATE_PLAY;
bool ret = updateChannel(false, "", "", "", 0);
if(ret)
{
// 先握手然后等helloCB之后再进行登录
Network::Bundle* pBundle =
Network::Bundle::ObjPool().createObject();
(*pBundle).newMessage(BaseappInterface::hello);
(*pBundle) << KBEVersion::versionString();
(*pBundle) << KBEVersion::scriptVersionString();
if(Network::g_channelExternalEncryptType == 1)
{
pBlowfishFilter_ = new Network::BlowfishFilter();
(*pBundle).appendBlob(pBlowfishFilter_->key());
pServerChannel_->pFilter(NULL);
}
else
{
std::string key = "";
(*pBundle).appendBlob(key);
}
pServerChannel_->pEndPoint()->send(pBundle);
Network::Bundle::ObjPool().reclaimObject(pBundle);
// ret = ClientObjectBase::loginGateWay();
}
}
break;
case C_STATE_LOGIN_GATEWAY:
state_ = C_STATE_PLAY;
if(!ClientObjectBase::loginGateWay())
{
WARNING_MSG("ClientApp::handleGameTick: loginGateWay is failed!\n");
return;
}
break;
case C_STATE_PLAY:
break;
default:
KBE_ASSERT(false);
break;
};
}
可以看到是根据不同的状态进行不同的动作的一个状态机。
3)事件派发器工作:
如下构造了一个全局线程池:
g_pThreadPool = new thread::ThreadPool();
添加一个任务:
g_pThreadPool->addTask(new KBEMainTask());
我们跟一跟KBEMainTask,看到其process接口(client/kbengine_dll/main.cpp):
virtual bool process()
{
while(g_pApp && !g_break)
{
g_pLock = new KBEngine::script::PyThreadStateLock;
if(targetID >= 0)
{
g_pApp->setTargetID(targetID);
targetID = -1;
}
g_inProcess = true;
g_pApp->processOnce(true);
g_inProcess = false;
SAFE_RELEASE(g_pLock);
}
g_break = true;
return false;
}
跟进g_pApp->processOnce()接口看看(lib/client_lib/clientapp.cpp):
int ClientApp::processOnce(bool shouldIdle)
{
return dispatcher_.processOnce(shouldIdle);
}
不难发现这就是我们在v0.0.2中提到过的事件派发器的工作入口,由此也解决了我们上面的疑问,计时器是在一个单独的线程中进行工作的(这也可以说是客户端和服务端的区别使然,GUI客户端的主线程几乎总是UI线程,如果不专门开辟新线程进行i/o之类的事件派发,那UI线程就被阻塞了)。
b)客户端OgreApplication app:
1)构造OgreApplication:
// Create application object
OgreApplication* app = new OgreApplication();
跟进OgreApplication的构造函数:
kbe_registerEventHandle(this);
这个函数是kbengine_dll提供的一个接口,跟进去看的话也很简单,就不贴代码了,基本就是注册一个事件handle(流程是山路十八弯,应用程序调用dll接口,dll内转给全局的g_pApp,然后再转给eventHandler_,现在我们不得不假设我们还不知道这个事件handle到底作甚的),这里和Timer一样的手法,通过handle从而给handler提供处理接口
2)运行app:
此后main函数的流程转移到app->go(),代码如下(app/OgreApplication.cpp):
void OgreApplication::go(void)
{
if (!setup())
return;
while(!mShutDown)
{
mRoot->renderOneFrame();
// 通知系统分派消息
Ogre::WindowEventUtilities::messagePump();
if(!g_hasEvent)
Sleep(1);
}
// clean up
destroyScene();
}
跟进setup:
bool OgreApplication::setup(void)
{
#ifdef _DEBUG
mRoot = new Ogre::Root("plugins_d.cfg");
#else
mRoot = new Ogre::Root();
#endif
setupResources();
if (!configure()) return false;
// Set default mipmap level (NB some APIs ignore this)
Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);
// Create any resource listeners (for loading screens)
createResourceListener();
// Load resources
loadResources();
createFrameListener();
mWindow->setDeactivateOnFocusChange(false);
changeSpace(new SpaceLogin(mRoot, mWindow, mInputManager, mTrayMgr));
return true;
};
可以看到changeSpace到了SpaceLogin,跟一下OgreApplication::changeSpace,可以看到它还调用了新的Space的setup接口。
一、登陆页面:
上面说到changeSpace的时候都会触发新页面的setup,我们看看SpaceLogin的setup (client/ogre/app/space.cpp):
bool Space::setup()
{
setupResources();
// Create the scene
createScene();
return true;
}
跟到SpaceLogin的createScene接口,从气氛上可以感受出就是设置页面的控件之类的(我看不懂Ogre的代码)。
此后的代码流程几乎全是event-driven了,所以我们得找到一个事件的入口点。
一、登陆ClientApp::login:
概读一下SpaceLogin(app/space_login.h/space_login.cpp)可以看到在buttonHit中,当点击“fast login”之后便触发kbe_login接口,代码如下(kbengine_dll/main.cpp):
bool kbe_login(const char* accountName, const char* passwd, const char* datas, KBEngine::uint32 datasize,
const char* ip, KBEngine::uint32port)
{
if(ip == NULL || port == 0)
{
ip = pconfig->ip();
port = pconfig->port();
}
kbe_lock();
std::string sdatas;
sdatas.assign((const char*)datas, datasize);
bool ret = g_pApp->login(accountName, passwd, sdatas, ip, port);
kbe_unlock();
return ret;
}
可以看到实际上负责实现登陆到kbengine服务端接口的是一个kbengine_dll内ClientApp 的全局实例的login接口(lib/client_lib/clientapp.cpp):
bool ClientApp::login(std::string accountName, std::string passwd, std::string datas,
std::string ip, KBEngine::uint32port)
{
connectedGateway_ = false;
if(canReset_)
reset();
clientDatas_ = datas;
bool ret = updateChannel(true, accountName, passwd, ip, port);
if(ret)
{
// 先握手然后等helloCB之后再进行登录
Network::Bundle* pBundle = Network::Bundle::ObjPool().createObject();
(*pBundle).newMessage(LoginappInterface::hello);
(*pBundle) << KBEVersion::versionString();
(*pBundle) << KBEVersion::scriptVersionString();
if(Network::g_channelExternalEncryptType == 1)
{
pBlowfishFilter_ = new Network::BlowfishFilter();
(*pBundle).appendBlob(pBlowfishFilter_->key());
}
else
{
std::string key = "";
(*pBundle).appendBlob(key);
}
pServerChannel_->pEndPoint()->send(pBundle);
Network::Bundle::ObjPool().reclaimObject(pBundle);
//ret = ClientObjectBase::login();
}
return ret;
}
这个接口调用了一下updateChannel,我们跟进去看看:
bool ClientApp::updateChannel(bool loginapp, std::string accountName, std::string passwd,
std::string ip, KBEngine::uint32port)
{
if(pServerChannel_->pEndPoint())
{
pServerChannel_->stopSend();
networkInterface().dispatcher().deregisterReadFileDescriptor(*pServerChannel_->pEndPo int());
networkInterface().deregisterChannel(pServerChannel_);
}
bool ret = loginapp ? (initLoginappChannel(accountName, passwd, ip, port) != NULL) : (initBaseappChannel() != NULL);
if(ret)
{
if(pTCPPacketReceiver_)
pTCPPacketReceiver_->pEndPoint(pServerChannel_->pEndPoint());
else
pTCPPacketReceiver_ = new
Network::TCPPacketReceiver(*pServerChannel_->pEndPoint(), networkInterface());
if(pTCPPacketSender_)
pTCPPacketSender_->pEndPoint(pServerChannel_->pEndPoint());
else
pTCPPacketSender_ = new
Network::TCPPacketSender(*pServerChannel_->pEndPoint(), networkInterface());
pServerChannel_->pPacketSender(pTCPPacketSender_);
networkInterface().registerChannel(pServerChannel_);
networkInterface().dispatcher().registerReadFileDescriptor(*pServerChannel_->pEndPoin t(), pTCPPacketReceiver_);
}
return ret;
}
比较核心的地方在于:
bool ret = loginapp ? (initLoginappChannel(accountName, passwd, ip, port) != NULL) : (initBaseappChannel() != NULL);
ClientApp::login接口内将loginapp置为true,所以会调用initLoginappChannel,我们跟看看:Network::Channel* ClientObjectBase::initLoginappChannel(std::string accountName, std::string passwd, std::string ip, KBEngine::uint32port)
{
Network::EndPoint* pEndpoint = Network::EndPoint::ObjPool().createObject();
pEndpoint->socket(SOCK_STREAM);
if (!pEndpoint->good())
{
ERROR_MSG("ClientObjectBase::initLoginappChannel: couldn't create a socket\n");
Network::EndPoint::ObjPool().reclaimObject(pEndpoint);
return NULL;
}
u_int32_t address;
Network::Address::string2ip(ip.c_str(), address);
if(pEndpoint->connect(htons(port), address) == -1)
{
ERROR_MSG(fmt::format("ClientObjectBase::initLoginappChannel: connect server is error({})!\n",
kbe_strerror()));
Network::EndPoint::ObjPool().reclaimObject(pEndpoint);
return NULL;
}
Network::Address addr(ip.c_str(), port);
pEndpoint->addr(addr);
pServerChannel_->pEndPoint(pEndpoint);
pEndpoint->setnonblocking(true);
pEndpoint->setnodelay(true);
password_ = passwd;
name_ = accountName;
lastSentActiveTickTime_ = timestamp();
return pServerChannel_;
}
基本可以理解为将pServerChannel_置于与服务端的LoginApp端点通信。
既然已经到了这,我们就顺便来看看initBaseappChannel:
Network::Channel* ClientObjectBase::initBaseappChannel()
{
Network::EndPoint* pEndpoint = Network::EndPoint::ObjPool().createObject();
pEndpoint->socket(SOCK_STREAM);
if (!pEndpoint->good())
{
ERROR_MSG("ClientObjectBase::initBaseappChannel: couldn't create a socket\n");
Network::EndPoint::ObjPool().reclaimObject(pEndpoint);
return NULL;
}
u_int32_t address;
Network::Address::string2ip(ip_.c_str(), address);
if(pEndpoint->connect(htons(port_), address) == -1)
{
ERROR_MSG(fmt::format("ClientObjectBase::initBaseappChannel: connect server is error({})!\n",
kbe_strerror()));
Network::EndPoint::ObjPool().reclaimObject(pEndpoint);
return NULL;
}
Network::Address addr(ip_.c_str(), port_);
pEndpoint->addr(addr);
pServerChannel_->pEndPoint(pEndpoint);
pEndpoint->setnonblocking(true);
pEndpoint->setnodelay(true);
connectedGateway_ = true;
lastSentActiveTickTime_ = timestamp();
lastSentUpdateDataTime_ = timestamp();
return pServerChannel_;
}
可以看到这个接口内将pServerChannel_连接到了新的端点,地址是ip_和port_,由于这个变量名使得我们看不出这到底是个啥的地址(代码里面也没注释出),所以我们对这个接口的探索就到此为止了。
二、发送LoginappInterface::hello:
我们继续回到app的login接口,根据刚才的理解得知,就是先将pServerChannel_和LoginApp连接(就是接口传入的ip/port),然后构造一个LoginappInterface::hello的Bundle通过pServerChannel_发送出去。之后依靠事件派发器分发网络读事件(processNetwork),再由TcpPacketReceiver接收数据并缓存到相应的Channel。再由Channel进行数据的处理(processTimers),根据数据包的id调用相应的messageHandler,messageHandler根据编译时构造的消息-处理映射调用相应的处理接口。(你晕了吗!?整理完这里我在火炬2刷了好几张图才算清醒一点)
三、等待ClientInterface::onHelloCB:
LoginappInterface::hello的响应包的id是ClientInterface::onHelloCB(这里可以配合服务端代码发现,在后面更详细的服务端解读里面会提及,现在靠这个名字应该就能感知到了),而onHello的处理接口是ClientApp::onHelloCB(其实是ClientObjectbase::onHelloCB),我们来两斤代码压压惊(lib/client_lib/clientobjectbase.cpp):
void ClientObjectBase::onHelloCB(Network::Channel* pChannel, MemoryStream& s)
{
std::string verInfo;
s >> verInfo;
std::string scriptVerInfo;
s >> scriptVerInfo;
std::string protocolMD5;
s >> protocolMD5;
std::string entityDefMD5;
s >> entityDefMD5;
COMPONENT_TYPE ctype;
s >> ctype;
INFO_MSG(fmt::format("ClientObjectBase::onHelloCB: verInfo={}, scriptVerInfo={}, protocolMD5={}, entityDefMD5={}, addr:{}\n",
verInfo, scriptVerInfo, protocolMD5, entityDefMD5, pChannel->c_str()));
onHelloCB_(pChannel, verInfo, scriptVerInfo, protocolMD5, entityDefMD5, ctype);
}
可以看到就是把MemoryStream反序列化了,把参数转给onHelloCB_(注意ctype变量),因为ClientApp重载了这个接口,所以我们去ClientApp::onHelloCB_看看:
void ClientApp::onHelloCB_(Network::Channel* pChannel, const std::string& verInfo, const std::string& scriptVerInfo, const std::string& protocolMD5, const
std::string& entityDefMD5,
COMPONENT_TYPE componentType)
{
if(Network::g_channelExternalEncryptType == 1)
{
pServerChannel_->pFilter(pBlowfishFilter_);
pBlowfishFilter_ = NULL;
}
if(componentType == LOGINAPP_TYPE)
{
state_ = C_STATE_LOGIN;
}
else
{
state_ = C_STATE_LOGIN_GATEWAY;
}
}
大意基本为根据返回onHelloCB消息的组件类型置state_为C_STATE_LOGIN或C_STA TE_LOGIN_GA TEW AY。
在异步/事件驱动模型里面,因为这个代码编排被打乱之后,会造成很多困扰,例如上面的发送hello和等待onHelloCB,本来是很自然的顺序流程,却不得不拆成两个函数,并且还要依靠一些状态变量来维系。解决这个问题有人用协程,有人用状态机,有人用队列……理论上来讲协程是最自然的解决手段(如果你还没用过这个,赶快去google),不过C++的协程至今为止都还比较薄弱,不像go/python之流内置协程语义,所以在C++工业工程里面用协程的几近于无。kbe选择了用状态机。
设置完状态之后等待计时器触发handleTimeout。
四、触发ClientApp::handl eTimeout:
五、发送LoginappInterface::login:
根据前面的分析可知,此时state_被置为C_STATE_LOGIN,可以看到这种状态下将会调用ClientObjectBase::login(lib/client_lib/clientobjectbase.cpp):
bool ClientObjectBase::login()
{
Network::Bundle* pBundle = Network::Bundle::ObjPool().createObject();
// 提交账号密码请求登录
(*pBundle).newMessage(LoginappInterface::login);
(*pBundle) << typeClient_;
(*pBundle).appendBlob(clientDatas_);
(*pBundle) << name_;
(*pBundle) << password_;
(*pBundle) << EntityDef::md5().getDigestStr();
pServerChannel_->send(pBundle);
connectedGateway_ = false;
return true;
}
六、等待ClientInterface::onLoginSuccessfully:
即发送LoginappInterface::login数据包(前面是LoginappInterface::hello,就是说直接调用ClientApp::login是发送hello,然后等待onHelloCB,然后再调用ClientObjectBase::login,当然这一切都是kbe自动的,跟应用没关系),然后等待onLoginSuccessfully/Failed(如果你是现在是凭直觉发现这一点的话,那你和我一样是幸运的,在后面的版本中会解读的),如下:
void ClientApp::onLoginSuccessfully(Network::Channel * pChannel, MemoryStream& s)
{
ClientObjectBase::onLoginSuccessfully(pChannel, s);
Config::getSingleton().writeAccountName(name_.c_str());
state_ = C_STATE_LOGIN_GATEWAY_CHANNEL;
}
我们跟进ClientObjectBase::onLoginSuccessfully(lib/client_lib/clientobjectbase.cpp):
void ClientObjectBase::onLoginSuccessfully(Network::Channel * pChannel, MemoryStream& s) {
std::string accountName;
s >> accountName;
s >> ip_;
s >> port_;
s.readBlob(serverDatas_);
connectedGateway_ = false;
INFO_MSG(fmt::format("ClientObjectBase::onLoginSuccessfully: {} addr={}:{}!\n", name_, ip_, port_));
EventData_LoginSuccess eventdata;
eventHandler_.fire(&eventdata);
gatewayIP_ = ip_;
gateWayPort_ = port_;
}
此时此刻我们终于搞清了ip_/port_是个甚,他们就是登陆后返回的所谓的网关(就是一个Baseapp)地址。
将状态置为C_STATE_LOGIN_GATEW AY_CHANNEL后等待handleTimeout
七、触发ClientApp::handl eTimeout:
此时状态置为C_STA TE_LOGIN_GATEW AY_CHANNEL,会执行这一段:
case C_STATE_LOGIN_GATEWAY_CHANNEL:
{
state_ = C_STATE_PLAY;
bool ret = updateChannel(false, "", "", "", 0);
if(ret)
{
// 先握手然后等helloCB之后再进行登录
Network::Bundle* pBundle =
Network::Bundle::ObjPool().createObject();
(*pBundle).newMessage(BaseappInterface::hello);
(*pBundle) << KBEVersion::versionString();
(*pBundle) << KBEVersion::scriptVersionString();
if(Network::g_channelExternalEncryptType == 1)
{
pBlowfishFilter_ = new Network::BlowfishFilter();
(*pBundle).appendBlob(pBlowfishFilter_->key());
pServerChannel_->pFilter(NULL);
}
else
{
std::string key = "";
(*pBundle).appendBlob(key);
}
pServerChannel_->pEndPoint()->send(pBundle);
Network::Bundle::ObjPool().reclaimObject(pBundle);
// ret = ClientObjectBase::loginGateWay();
}
}
break;
updateChannel中loginapp此次调用被置为false,所以将pServerChannel_连接到ip_/port_(登陆返回的网关地址)端点。
然后再次发送hello消息,不过此时发送的是BaseappInterface::hello,然后等待ClientInterface::onHelloCB(是的,跟LoginappInterface::hello返回一样的消息的id),只不过他们的ComponentType值是不一样的。
八、再次等待ClientInterface::onHelloCB:
和iii中一样的流程,只是这次状态state_被置为C_STA TE_LOGIN_GATEW AY,然后等待ClientApp::handleTimeout。
九、触发ClientApp::handl eTimeout:
此时状态state_被置为C_STA TE_LOGIN_GATEW AY,所以执行下面的代码:
case C_STATE_LOGIN_GATEWAY:
state_ = C_STATE_PLAY;
if(!ClientObjectBase::loginGateWay())
{
WARNING_MSG("ClientApp::handleGameTick: loginGateWay is failed!\n");
return;
}
break;
我们跟进ClientObjectBase::loginGateWay(lib/client_lib/clientobjectbase.cpp):
bool ClientObjectBase::loginGateWay()
{
// 请求登录网关, 能走到这里来一定是连接了网关
connectedGateway_ = true;
Network::Bundle* pBundle = Network::Bundle::ObjPool().createObject();
(*pBundle).newMessage(BaseappInterface::loginGateway);
(*pBundle) << name_;
(*pBundle) << password_;
pServerChannel_->send(pBundle);
return true;
}
可知是发送BaseappInterface::loginGateway数据包给网关,然后等待ClientInterface::onCreatedProxies/ClientInterface::onLoginGatewayFailed(我们暂时凭小宇宙感知得出这个结论)。
十、等待ClientInterface::onCreatedProxies:
代码如下(lib/client_lib/clientobjectbase.cpp):
void ClientObjectBase::onCreatedProxies(Network::Channel * pChannel, uint64rndUUID, ENTITY_ID eid, std::string& entityType)
{
client::Entity* entity = pEntities_->find(eid);
if(entity != NULL)
{
WARNING_MSG(fmt::format("ClientObject::onCreatedProxies({}): rndUUID={} eid={} entityType={}. has exist!\n",
name_, rndUUID, eid, entityType));
return;
}
if(entityID_ == 0)
{
EventData_LoginGatewaySuccess eventdata;
eventHandler_.fire(&eventdata);
}
BUFFEREDMESSAGE::iterator iter = bufferedCreateEntityMessage_.find(eid);
bool hasBufferedMessage = (iter != bufferedCreateEntityMessage_.end());
// 能走到这里来一定是连接了网关
connectedGateway_ = true;
entityID_ = eid;
rndUUID_ = rndUUID;
INFO_MSG(fmt::format("ClientObject::onCreatedProxies({}): rndUUID={} eid={} entityType={}!\n",
name_, rndUUID, eid, entityType));
// 设置entity的baseMailbox
EntityMailbox* mailbox = new
EntityMailbox(EntityDef::findScriptModule(entityType.c_str()),
NULL, appID(), eid, MAILBOX_TYPE_BASE);
client::Entity* pEntity = createEntity(entityType.c_str(), NULL, !hasBufferedMessage, eid, true, mailbox, NULL);
KBE_ASSERT(pEntity != NULL);
if(hasBufferedMessage)
{
// 先更新属性再初始化脚本
this->onUpdatePropertys(pChannel, *iter->second.get());
bufferedCreateEntityMessage_.erase(iter);
pEntity->initializeEntity(NULL);
SCRIPT_ERROR_CHECK();
}
}
值得注意的是此时并未再改变任何状态,我们跟进这一段代码:
if(entityID_ == 0)
{
EventData_LoginGatewaySuccess eventdata;
eventHandler_.fire(&eventdata);
}
跟进eventHandler_.fire(lib/client_lib/event.cpp):
void EventHandler::fire(const EventData* lpEventData)
{
EVENT_HANDLES::iterator iter = eventHandles_.begin();
for(; iter != eventHandles_.end(); ++iter)
{
(*iter)->kbengine_onEvent(lpEventData);
}
}
十一、缓存kbe事件数据
联想前面构造OgreApplication时注册的event handle可知此接口之作用,我们跟进OgreApplication::kbengine_onEvent(client/ogre/app/OgreApplication.cpp):
void OgreApplication::kbengine_onEvent(const KBEngine::EventData* lpEventData)
{
KBEngine::EventData* peventdata = KBEngine::copyKBEngineEvent(lpEventData);
if(peventdata)
{
boost::mutex::scoped_lock lock(g_spaceMutex);
events_.push(std::tr1::shared_ptr
g_hasEvent = true;
}
}
可知这个接口就是将一个EventData推入到events_队列,那问题又来了,何时处理events_呢?
十二、分派kbe事件数据队列
不管是通过搜索通读,我们找到如下代码(client/ogre/OgreApplication.cpp):
bool OgreApplication::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
if(g_space == NULL)
{
return BaseApplication::frameRenderingQueued(evt);
}
while(true)
{
g_spaceMutex.lock();
if(events_.empty())
{
g_spaceMutex.unlock();
break;
}
std::tr1::shared_ptr
events_.pop();
g_spaceMutex.unlock();
KBEngine::EventID id = pEventData->id;
if(id == CLIENT_EVENT_SERVER_CLOSED)
{
//OgreApplication::getSingleton().changeSpace(new SpaceAvatarSelect(mRoot, mWindow, mInputManager, mTrayMgr));
//break;
}
// 如果需要在本线程访问脚本层则需要锁住引擎
if(id == CLIENT_EVENT_SCRIPT)
{
kbe_lock();
}
g_space->kbengine_onEvent(pEventData.get());
if(id == CLIENT_EVENT_SCRIPT)
{
kbe_unlock();
}
g_hasEvent = false;
}
if(!g_space->frameRenderingQueued(evt))
return true;