KBEngine warring项目源码阅读(二) 登录和baseapp的负载均衡
原本不打算把登录拿出来写的,但是阅读登录部分的代码的时候发现登录和注册还不太一回事,因为登录涉及到分配baseapp的ip,负载均衡的实现,所以水一下。
流程图:

和上次一样,先是找unity控件

找到ui.cs下的login
void login()
{
Common.DEBUG_MSG("login is Click, name=" + username.input.text + ", password=" + password.input.text + "!"); log_label.obj.text = "请求连接服务器...";
log_label.obj.color = UnityEngine.Color.green;
if(username.input.text == "" || username.input.text.Length > )
{
log_label.obj.text = "用户名或者邮箱地址不合法。";
log_label.obj.color = UnityEngine.Color.red;
Common.WARNING_MSG("ui::login: invalid username!");
return;
} if(password.input.text.Length < || password.input.text.Length > )
{
log_label.obj.text = "密码不合法, 长度应该在6-16位之间。";
log_label.obj.color = UnityEngine.Color.red;
Common.WARNING_MSG("ui::login: invalid reg_password!");
return;
} KBEngine.Event.fireIn("login", username.input.text, password.input.text, System.Text.Encoding.UTF8.GetBytes("kbengine_unity_warring"));
log_label.obj.text = "连接成功,等待处理请稍后...";
}
根据上篇文章思路找到Kbengine.cs下的login_loginapp
/*
登录到服务端(loginapp), 登录成功后还必须登录到网关(baseapp)登录流程才算完毕
*/
public void login_loginapp(bool noconnect)
{
if(noconnect)
{
reset();
_networkInterface.connectTo(_args.ip, _args.port, onConnectTo_loginapp_callback, null);
}
else
{
Dbg.DEBUG_MSG("KBEngine::login_loginapp(): send login! username=" + username);
Bundle bundle = Bundle.createObject();
bundle.newMessage(Message.messages["Loginapp_login"]);
bundle.writeInt8((sbyte)_args.clientType);
bundle.writeBlob(KBEngineApp.app._clientdatas);
bundle.writeString(username);
bundle.writeString(password);
bundle.send(_networkInterface);
}
}
去服务器的loginapp项目找login
//-------------------------------------------------------------------------------------
void Loginapp::login(Network::Channel* pChannel, MemoryStream& s)
{
AUTO_SCOPED_PROFILE("login"); COMPONENT_CLIENT_TYPE ctype;
CLIENT_CTYPE tctype = UNKNOWN_CLIENT_COMPONENT_TYPE;
std::string loginName;
std::string password;
std::string datas; // 前端类别
s >> tctype;
ctype = static_cast<COMPONENT_CLIENT_TYPE>(tctype); // 附带数据
s.readBlob(datas); // 帐号登录名
s >> loginName; // 密码
s >> password; loginName = KBEngine::strutil::kbe_trim(loginName);
if(loginName.size() == )
{
INFO_MSG("Loginapp::login: loginName is NULL.\n");
_loginFailed(pChannel, loginName, SERVER_ERR_NAME, datas, true);
s.done();
return;
} if(loginName.size() > ACCOUNT_NAME_MAX_LENGTH)
{
INFO_MSG(fmt::format("Loginapp::login: loginName is too long, size={}, limit={}.\n",
loginName.size(), ACCOUNT_NAME_MAX_LENGTH)); _loginFailed(pChannel, loginName, SERVER_ERR_NAME, datas, true);
s.done();
return;
} if(password.size() > ACCOUNT_PASSWD_MAX_LENGTH)
{
INFO_MSG(fmt::format("Loginapp::login: password is too long, size={}, limit={}.\n",
password.size(), ACCOUNT_PASSWD_MAX_LENGTH)); _loginFailed(pChannel, loginName, SERVER_ERR_PASSWORD, datas, true);
s.done();
return;
} if(datas.size() > ACCOUNT_DATA_MAX_LENGTH)
{
INFO_MSG(fmt::format("Loginapp::login: bindatas is too long, size={}, limit={}.\n",
datas.size(), ACCOUNT_DATA_MAX_LENGTH)); _loginFailed(pChannel, loginName, SERVER_ERR_OP_FAILED, datas, true);
s.done();
return;
} // 首先必须baseappmgr和dbmgr都已经准备完毕了。
Components::ComponentInfos* baseappmgrinfos = Components::getSingleton().getBaseappmgr();
if(baseappmgrinfos == NULL || baseappmgrinfos->pChannel == NULL || baseappmgrinfos->cid == )
{
datas = "";
_loginFailed(pChannel, loginName, SERVER_ERR_SRV_NO_READY, datas, true);
s.done();
return;
} Components::ComponentInfos* dbmgrinfos = Components::getSingleton().getDbmgr();
if(dbmgrinfos == NULL || dbmgrinfos->pChannel == NULL || dbmgrinfos->cid == )
{
datas = "";
_loginFailed(pChannel, loginName, SERVER_ERR_SRV_NO_READY, datas, true);
s.done();
return;
} if(!g_kbeSrvConfig.getDBMgr().allowEmptyDigest)
{
std::string clientDigest; if(s.length() > )
s >> clientDigest; if(clientDigest.size() > )
{
if(clientDigest != digest_)
{
INFO_MSG(fmt::format("Loginapp::login: loginName({}), digest not match. curr({}) != dbmgr({})\n",
loginName, clientDigest, digest_)); datas = "";
_loginFailed(pChannel, loginName, SERVER_ERR_ENTITYDEFS_NOT_MATCH, datas, true);
return;
}
}
else
{
//WARNING_MSG(fmt::format("Loginapp::login: loginName={} no check entitydefs!\n", loginName));
}
} s.done(); if(shuttingdown_ != SHUTDOWN_STATE_STOP)
{
INFO_MSG(fmt::format("Loginapp::login: shutting down, {} login failed!\n", loginName)); datas = "";
_loginFailed(pChannel, loginName, SERVER_ERR_IN_SHUTTINGDOWN, datas, true);
return;
} if(initProgress_ < .f)
{
datas = fmt::format("initProgress: {}", initProgress_);
_loginFailed(pChannel, loginName, SERVER_ERR_SRV_STARTING, datas, true);
return;
} // 把请求交由脚本处理
SCOPED_PROFILE(SCRIPTCALL_PROFILE);
PyObject* pyResult = PyObject_CallMethod(getEntryScript().get(),
const_cast<char*>("onReuqestLogin"),
const_cast<char*>("ssby#"),
loginName.c_str(),
password.c_str(),
tctype,
datas.c_str(), datas.length()); if(pyResult != NULL)
{
bool login_check = true;
if(PySequence_Check(pyResult) && PySequence_Size(pyResult) == )
{
char* sname;
char* spassword;
char *extraDatas;
Py_ssize_t extraDatas_size = ;
SERVER_ERROR_CODE error; if(PyArg_ParseTuple(pyResult, "H|s|s|b|y#", &error, &sname, &spassword, &tctype, &extraDatas, &extraDatas_size) == -)
{
ERROR_MSG(fmt::format("Loginapp::login: {}.onReuqestLogin, Return value error! loginName={}\n",
g_kbeSrvConfig.getLoginApp().entryScriptFile, loginName)); login_check = false;
_loginFailed(pChannel, loginName, SERVER_ERR_OP_FAILED, datas, true);
} if(login_check)
{
loginName = sname;
password = spassword; if (extraDatas && extraDatas_size > )
datas.assign(extraDatas, extraDatas_size);
else
SCRIPT_ERROR_CHECK();
} if(error != SERVER_SUCCESS)
{
login_check = false;
_loginFailed(pChannel, loginName, error, datas, true);
} if(loginName.size() == )
{
INFO_MSG("Loginapp::login: loginName is NULL.\n");
_loginFailed(pChannel, loginName, SERVER_ERR_NAME, datas, true);
s.done();
return;
}
}
else
{
ERROR_MSG(fmt::format("Loginapp::login: {}.onReuqestLogin, Return value error, must be errorcode or tuple! loginName={}\n",
g_kbeSrvConfig.getLoginApp().entryScriptFile, loginName)); login_check = false;
_loginFailed(pChannel, loginName, SERVER_ERR_OP_FAILED, datas, true);
} Py_DECREF(pyResult); if(!login_check)
return;
}
else
{
SCRIPT_ERROR_CHECK();
_loginFailed(pChannel, loginName, SERVER_ERR_OP_FAILED, datas, true);
} PendingLoginMgr::PLInfos* ptinfos = pendingLoginMgr_.find(loginName);
if(ptinfos != NULL)
{
datas = "";
_loginFailed(pChannel, loginName, SERVER_ERR_BUSY, datas, true);
return;
} ptinfos = new PendingLoginMgr::PLInfos;
ptinfos->ctype = ctype;
ptinfos->datas = datas;
ptinfos->accountName = loginName;
ptinfos->password = password;
ptinfos->addr = pChannel->addr();
pendingLoginMgr_.add(ptinfos); if(ctype < UNKNOWN_CLIENT_COMPONENT_TYPE || ctype >= CLIENT_TYPE_END)
ctype = UNKNOWN_CLIENT_COMPONENT_TYPE; INFO_MSG(fmt::format("Loginapp::login: new client[{0}], loginName={1}, datas={2}.\n",
COMPONENT_CLIENT_NAME[ctype], loginName, datas)); pChannel->extra(loginName); // 向dbmgr查询用户合法性
Network::Bundle* pBundle = Network::Bundle::createPoolObject();
(*pBundle).newMessage(DbmgrInterface::onAccountLogin);
(*pBundle) << loginName << password;
(*pBundle).appendBlob(datas);
dbmgrinfos->pChannel->send(pBundle);
}
这个函数进行了一系列的检查,确定合法后向dbmgr发送一个登陆请求包“(*pBundle).newMessage(DbmgrInterface::onAccountLogin);
然后在dbmgr.cpp中
//-------------------------------------------------------------------------------------
void Dbmgr::onAccountLogin(Network::Channel* pChannel, KBEngine::MemoryStream& s)
{
std::string loginName, password, datas;
s >> loginName >> password;
s.readBlob(datas); if(loginName.size() == )
{
ERROR_MSG("Dbmgr::onAccountLogin: loginName is empty.\n");
return;
} pInterfacesAccountHandler_->loginAccount(pChannel, loginName, password, datas);
}
转到interfaces_handler.cpp中
//-------------------------------------------------------------------------------------
bool InterfacesHandler_Dbmgr::loginAccount(Network::Channel* pChannel, std::string& loginName,
std::string& password, std::string& datas)
{
std::string dbInterfaceName = Dbmgr::getSingleton().selectAccountDBInterfaceName(loginName); thread::ThreadPool* pThreadPool = DBUtil::pThreadPool(dbInterfaceName);
if (!pThreadPool)
{
ERROR_MSG(fmt::format("InterfacesHandler_Dbmgr::loginAccount: not found dbInterface({})!\n",
dbInterfaceName)); return false;
} pThreadPool->addTask(new DBTaskAccountLogin(pChannel->addr(),
loginName, loginName, password, SERVER_SUCCESS, datas, datas, true)); return true;
}
点开任务DBTaskAccountLogin,基于上篇文章说过的理由只看这两个函数
bool DBTaskAccountLogin::db_thread_process()
{
// 如果Interfaces已经判断不成功就没必要继续下去
if(retcode_ != SERVER_SUCCESS)
{
ERROR_MSG(fmt::format("DBTaskAccountLogin::db_thread_process(): interfaces is failed!\n"));
return false;
} retcode_ = SERVER_ERR_OP_FAILED; if(accountName_.size() == )
{
ERROR_MSG(fmt::format("DBTaskAccountLogin::db_thread_process(): accountName is NULL!\n"));
retcode_ = SERVER_ERR_NAME;
return false;
} ScriptDefModule* pModule = EntityDef::findScriptModule(DBUtil::accountScriptName()); if(pModule == NULL)
{
ERROR_MSG(fmt::format("DBTaskAccountLogin::db_thread_process(): not found account script[{}], login[{}] failed!\n",
DBUtil::accountScriptName(), accountName_)); retcode_ = SERVER_ERR_SRV_NO_READY;
return false;
} EntityTables& entityTables = EntityTables::findByInterfaceName(pdbi_->name());
KBEEntityLogTable* pELTable = static_cast<KBEEntityLogTable*>
(entityTables.findKBETable("kbe_entitylog")); KBE_ASSERT(pELTable); KBEAccountTable* pTable = static_cast<KBEAccountTable*>(entityTables.findKBETable("kbe_accountinfos"));
KBE_ASSERT(pTable); ACCOUNT_INFOS info;
info.dbid = ;
info.flags = ;
info.deadline = ; if(!pTable->queryAccount(pdbi_, accountName_, info))
{
flags_ = info.flags;
deadline_ = info.deadline; if(ACCOUNT_TYPE(g_kbeSrvConfig.getLoginApp().account_type) != ACCOUNT_TYPE_NORMAL)
{
if (email_isvalid(accountName_.c_str()))
{
ERROR_MSG(fmt::format("DBTaskAccountLogin::db_thread_process(): account[{}] is email, autocreate failed!\n",
accountName_)); retcode_ = SERVER_ERR_CANNOT_USE_MAIL;
return false;
}
} if (g_kbeSrvConfig.getDBMgr().notFoundAccountAutoCreate ||
(Network::Address::NONE != g_kbeSrvConfig.interfacesAddr() && !needCheckPassword_/*第三方处理成功则自动创建账号*/))
{
if(!DBTaskCreateAccount::writeAccount(pdbi_, accountName_, password_, postdatas_, info) || info.dbid == || info.flags != ACCOUNT_FLAG_NORMAL)
{
ERROR_MSG(fmt::format("DBTaskAccountLogin::db_thread_process(): writeAccount[{}] is error!\n",
accountName_)); retcode_ = SERVER_ERR_DB;
return false;
} INFO_MSG(fmt::format("DBTaskAccountLogin::db_thread_process(): not found account[{}], autocreate successfully!\n",
accountName_)); info.password = KBE_MD5::getDigest(password_.data(), (int)password_.length());
}
else
{
ERROR_MSG(fmt::format("DBTaskAccountLogin::db_thread_process(): not found account[{}], login failed!\n",
accountName_)); retcode_ = SERVER_ERR_NOT_FOUND_ACCOUNT;
return false;
}
} if(info.dbid == )
return false; if(info.flags != ACCOUNT_FLAG_NORMAL)
{
flags_ = info.flags;
return false;
} if (needCheckPassword_ || Network::Address::NONE == g_kbeSrvConfig.interfacesAddr())
{
if (kbe_stricmp(info.password.c_str(), KBE_MD5::getDigest(password_.data(), (int)password_.length()).c_str()) != )
{
retcode_ = SERVER_ERR_PASSWORD;
return false;
}
} pTable->updateCount(pdbi_, accountName_, info.dbid); retcode_ = SERVER_ERR_ACCOUNT_IS_ONLINE;
KBEEntityLogTable::EntityLog entitylog;
bool success = !pELTable->queryEntity(pdbi_, info.dbid, entitylog, pModule->getUType()); // 如果有在线纪录
if(!success)
{
componentID_ = entitylog.componentID;
entityID_ = entitylog.entityID;
}
else
{
retcode_ = SERVER_SUCCESS;
} dbid_ = info.dbid;
flags_ = info.flags;
deadline_ = info.deadline;
return false;
} //-------------------------------------------------------------------------------------
thread::TPTask::TPTaskState DBTaskAccountLogin::presentMainThread()
{
DEBUG_MSG(fmt::format("Dbmgr::onAccountLogin:loginName={0}, accountName={1}, success={2}, componentID={3}, dbid={4}, flags={5}, deadline={6}.\n",
loginName_,
accountName_,
retcode_,
componentID_,
dbid_,
flags_,
deadline_
)); // 一个用户登录, 构造一个数据库查询指令并加入到执行队列, 执行完毕将结果返回给loginapp
Network::Bundle* pBundle = Network::Bundle::createPoolObject();
(*pBundle).newMessage(LoginappInterface::onLoginAccountQueryResultFromDbmgr); (*pBundle) << retcode_;
(*pBundle) << loginName_;
(*pBundle) << accountName_;
(*pBundle) << password_;
(*pBundle) << componentID_; // 如果大于0则表示账号还存活在某个baseapp上
(*pBundle) << entityID_;
(*pBundle) << dbid_;
(*pBundle) << flags_;
(*pBundle) << deadline_;
(*pBundle).appendBlob(getdatas_); if(!this->send(pBundle))
{
ERROR_MSG(fmt::format("DBTaskAccountLogin::presentMainThread: channel({}) not found.\n", addr_.c_str()));
Network::Bundle::reclaimPoolObject(pBundle);
} return DBTask::presentMainThread();
}
阅读完以后知道要点是queryAccount,查询完毕以后的调用函数是LoginappInterface::onLoginAccountQueryResultFromDbmgr
//-------------------------------------------------------------------------------------
bool KBEAccountTableMysql::queryAccount(DBInterface * pdbi, const std::string& name, ACCOUNT_INFOS& info)
{
std::string sqlstr = "select entityDBID, password, flags, deadline from kbe_accountinfos where accountName=\""; char* tbuf = new char[name.size() * + ]; mysql_real_escape_string(static_cast<DBInterfaceMysql*>(pdbi)->mysql(),
tbuf, name.c_str(), name.size()); sqlstr += tbuf;
sqlstr += "\" or email=\"";
sqlstr += tbuf;
sqlstr += "\" LIMIT 1";
SAFE_RELEASE_ARRAY(tbuf); // 如果查询失败则返回存在, 避免可能产生的错误
if(!pdbi->query(sqlstr.c_str(), sqlstr.size(), false))
return true; info.dbid = ;
MYSQL_RES * pResult = mysql_store_result(static_cast<DBInterfaceMysql*>(pdbi)->mysql());
if(pResult)
{
MYSQL_ROW arow = mysql_fetch_row(pResult);
if(arow != NULL)
{
KBEngine::StringConv::str2value(info.dbid, arow[]);
info.name = name;
info.password = arow[]; KBEngine::StringConv::str2value(info.flags, arow[]);
KBEngine::StringConv::str2value(info.deadline, arow[]);
} mysql_free_result(pResult);
} return info.dbid > ;
}
这里十分出乎我的意料,竟然直接读取了数据库,按道理来说这里读取内存更好些。
好吧,把疑问暂且放下,我们回头来看onLoginAccountQueryResultFromDbmgr
//-------------------------------------------------------------------------------------
void Loginapp::onLoginAccountQueryResultFromDbmgr(Network::Channel* pChannel, MemoryStream& s)
{
if(pChannel->isExternal())
return; std::string loginName, accountName, password, datas;
SERVER_ERROR_CODE retcode = SERVER_SUCCESS;
COMPONENT_ID componentID;
ENTITY_ID entityID;
DBID dbid;
uint32 flags;
uint64 deadline; s >> retcode; // 登录名既登录时客户端输入的名称, 账号名则是dbmgr查询得到的名称
// 这个机制用于一个账号多名称系统或者多个第三方账号系统登入服务器
// accountName为本游戏服务器账号所绑定的终身名称
// 客户端得到baseapp地址的同时也会返回这个账号名称
// 客户端登陆baseapp应该使用这个账号名称登陆
s >> loginName;
s >> accountName; s >> password;
s >> componentID;
s >> entityID;
s >> dbid;
s >> flags;
s >> deadline; s.readBlob(datas); //DEBUG_MSG(fmt::format("Loginapp::onLoginAccountQueryResultFromDbmgr: loginName={}.\n",
// loginName)); if((flags & ACCOUNT_FLAG_LOCK) > )
{
_loginFailed(NULL, loginName, SERVER_ERR_ACCOUNT_LOCK, datas);
return;
} if((flags & ACCOUNT_FLAG_NOT_ACTIVATED) > )
{
_loginFailed(NULL, loginName, SERVER_ERR_ACCOUNT_NOT_ACTIVATED, datas);
return;
} if(deadline > && ::time(NULL) - deadline <= )
{
_loginFailed(NULL, loginName, SERVER_ERR_ACCOUNT_DEADLINE, datas);
return;
} PendingLoginMgr::PLInfos* infos = pendingLoginMgr_.find(loginName);
if(infos == NULL)
{
_loginFailed(NULL, loginName, SERVER_ERR_SRV_OVERLOAD, datas);
return;
} // 把请求交由脚本处理
SCOPED_PROFILE(SCRIPTCALL_PROFILE);
PyObject* pyResult = PyObject_CallMethod(getEntryScript().get(),
const_cast<char*>("onLoginCallbackFromDB"),
const_cast<char*>("ssHy#"),
loginName.c_str(),
accountName.c_str(),
retcode,
datas.c_str(), datas.length()); if(pyResult != NULL)
{
Py_DECREF(pyResult);
}
else
{
SCRIPT_ERROR_CHECK();
} infos->datas = datas; Network::Channel* pClientChannel = this->networkInterface().findChannel(infos->addr);
if(pClientChannel)
pClientChannel->extra(""); if(retcode != SERVER_SUCCESS && entityID == && componentID == )
{
_loginFailed(NULL, loginName, retcode, datas);
return;
} // 获得baseappmgr地址。
Components::COMPONENTS& cts = Components::getSingleton().getComponents(BASEAPPMGR_TYPE);
Components::ComponentInfos* baseappmgrinfos = NULL;
if(cts.size() > )
baseappmgrinfos = &(*cts.begin()); if(baseappmgrinfos == NULL || baseappmgrinfos->pChannel == NULL || baseappmgrinfos->cid == )
{
_loginFailed(NULL, loginName, SERVER_ERR_SRV_NO_READY, datas);
return;
} // 如果大于0则说明当前账号仍然存活于某个baseapp上
if(componentID > )
{
Network::Bundle* pBundle = Network::Bundle::createPoolObject();
(*pBundle).newMessage(BaseappmgrInterface::registerPendingAccountToBaseappAddr);
(*pBundle) << componentID << loginName << accountName << password << entityID << dbid << flags << deadline << infos->ctype;
(*pBundle).appendBlob(infos->datas);
baseappmgrinfos->pChannel->send(pBundle);
return;
}
else
{
// 注册到baseapp并且获取baseapp的地址
Network::Bundle* pBundle = Network::Bundle::createPoolObject();
(*pBundle).newMessage(BaseappmgrInterface::registerPendingAccountToBaseapp); (*pBundle) << loginName;
(*pBundle) << accountName;
(*pBundle) << password;
(*pBundle) << dbid;
(*pBundle) << flags;
(*pBundle) << deadline;
(*pBundle) << infos->ctype;
(*pBundle).appendBlob(infos->datas);
baseappmgrinfos->pChannel->send(pBundle);
}
}
好吧,戏肉来了,终于到了从BaseappMgr请求BaseApp节点的地方,看下它是怎么做负载均衡的
//-------------------------------------------------------------------------------------
void Baseappmgr::registerPendingAccountToBaseapp(Network::Channel* pChannel, MemoryStream& s)
{
std::string loginName;
std::string accountName;
std::string password;
std::string datas;
DBID entityDBID;
uint32 flags;
uint64 deadline;
COMPONENT_TYPE componentType; s >> loginName >> accountName >> password >> entityDBID >> flags >> deadline >> componentType;
s.readBlob(datas); Components::ComponentInfos* cinfos = Components::getSingleton().findComponent(pChannel);
if(cinfos == NULL || cinfos->pChannel == NULL)
{
ERROR_MSG("Baseappmgr::registerPendingAccountToBaseapp: not found loginapp!\n");
return;
} pending_logins_[loginName] = cinfos->cid; updateBestBaseapp(); if (bestBaseappID_ == && numLoadBalancingApp() == )
{
ERROR_MSG(fmt::format("Baseappmgr::registerPendingAccountToBaseapp: Unable to allocate baseapp for load balancing! baseappSize={}, accountName={}.\n",
baseapps_.size(), loginName));
} ENTITY_ID eid = ;
cinfos = Components::getSingleton().findComponent(BASEAPP_TYPE, bestBaseappID_); if (cinfos == NULL || cinfos->pChannel == NULL || cinfos->state != COMPONENT_STATE_RUN)
{
Network::Bundle* pBundle = Network::Bundle::createPoolObject();
ForwardItem* pFI = new AppForwardItem(); pFI->pBundle = pBundle;
(*pBundle).newMessage(BaseappInterface::registerPendingLogin);
(*pBundle) << loginName << accountName << password << eid << entityDBID << flags << deadline << componentType;
pBundle->appendBlob(datas); int runstate = -;
if (cinfos)
runstate = (int)cinfos->state; WARNING_MSG(fmt::format("Baseappmgr::registerPendingAccountToBaseapp: not found baseapp({}, runstate={}, pChannel={}), message is buffered.\n",
bestBaseappID_, runstate, (cinfos && cinfos->pChannel ? cinfos->pChannel->c_str() : "NULL"))); pFI->pHandler = NULL;
forward_anywhere_baseapp_messagebuffer_.push(pFI);
return;
} std::map< COMPONENT_ID, Baseapp >::iterator baseapps_iter = baseapps_.find(bestBaseappID_); DEBUG_MSG(fmt::format("Baseappmgr::registerPendingAccountToBaseapp:{}. allocBaseapp={}, numEntities={}.\n",
accountName, bestBaseappID_, (bestBaseappID_ > ? baseapps_iter->second.numEntities() : ))); Network::Bundle* pBundle = Network::Bundle::createPoolObject();
(*pBundle).newMessage(BaseappInterface::registerPendingLogin);
(*pBundle) << loginName << accountName << password << eid << entityDBID << flags << deadline << componentType;
pBundle->appendBlob(datas);
cinfos->pChannel->send(pBundle); // 预先将实体数量增加
if (baseapps_iter != baseapps_.end())
{
baseapps_iter->second.incNumProxices();
}
}
在函数updateBestBaseapp();中选取了bestBaseappID_ ,然后通过BaseappInterface::registerPendingLogin将对应的数据发给了对应的BaseApp,我们先看下这个选取过程是如何进行的。
//-------------------------------------------------------------------------------------
void Baseappmgr::updateBestBaseapp()
{
bestBaseappID_ = findFreeBaseapp();
} //-------------------------------------------------------------------------------------
COMPONENT_ID Baseappmgr::findFreeBaseapp()
{
std::map< COMPONENT_ID, Baseapp >::iterator iter = baseapps_.begin();
COMPONENT_ID cid = ; float minload = .f;
ENTITY_ID numEntities = 0x7fffffff; for(; iter != baseapps_.end(); ++iter)
{
if ((iter->second.flags() & APP_FLAGS_NOT_PARTCIPATING_LOAD_BALANCING) > )
continue; // 首先进程必须活着且初始化完毕
if(!iter->second.isDestroyed() && iter->second.initProgress() > .f)
{
// 如果没有任何实体则无条件分配
if(iter->second.numEntities() == )
return iter->first; // 比较并记录负载最小的进程最终被分配
if(minload > iter->second.load() ||
(minload == iter->second.load() && numEntities > iter->second.numEntities()))
{
cid = iter->first; numEntities = iter->second.numEntities();
minload = iter->second.load();
}
}
} return cid;
}
可见负载均衡策略如下:
1、首先进程必须活着且初始化完毕
2、如果没有任何实体则无条件分配
3、比较并记录负载最小的进程最终被分配
到这里,我们回头看BaseApp是如何处理BaseAppMgr发来的消息的
//-------------------------------------------------------------------------------------
void Baseapp::registerPendingLogin(Network::Channel* pChannel, KBEngine::MemoryStream& s)
{
if(pChannel->isExternal())
{
s.done();
return;
} std::string loginName;
std::string accountName;
std::string password;
std::string datas;
ENTITY_ID entityID;
DBID entityDBID;
uint32 flags;
uint64 deadline;
COMPONENT_TYPE componentType; s >> loginName >> accountName >> password >> entityID >> entityDBID >> flags >> deadline >> componentType;
s.readBlob(datas); Network::Bundle* pBundle = Network::Bundle::createPoolObject();
(*pBundle).newMessage(BaseappmgrInterface::onPendingAccountGetBaseappAddr); (*pBundle) << loginName;
(*pBundle) << accountName; if(strlen((const char*)&g_kbeSrvConfig.getBaseApp().externalAddress) > )
{
(*pBundle) << g_kbeSrvConfig.getBaseApp().externalAddress;
}
else
{
(*pBundle) << inet_ntoa((struct in_addr&)networkInterface().extaddr().ip);
} (*pBundle) << this->networkInterface().extaddr().port;
pChannel->send(pBundle); PendingLoginMgr::PLInfos* ptinfos = new PendingLoginMgr::PLInfos;
ptinfos->accountName = accountName;
ptinfos->password = password;
ptinfos->entityID = entityID;
ptinfos->entityDBID = entityDBID;
ptinfos->flags = flags;
ptinfos->deadline = deadline;
ptinfos->ctype = (COMPONENT_CLIENT_TYPE)componentType;
ptinfos->datas = datas;
pendingLoginMgr_.add(ptinfos);
}
做了两件事情,
第一件是将本节点的外网IP和端口号加到消息里发给BaseAppMgr,消息名是onPendingAccountGetBaseappAddr
第二件事情是将登陆消息加到pendingLoginMgr中
//-------------------------------------------------------------------------------------
void Baseappmgr::onPendingAccountGetBaseappAddr(Network::Channel* pChannel,
std::string& loginName, std::string& accountName, std::string& addr, uint16 port)
{
sendAllocatedBaseappAddr(pChannel, loginName, accountName, addr, port);
} //-------------------------------------------------------------------------------------
void Baseappmgr::sendAllocatedBaseappAddr(Network::Channel* pChannel,
std::string& loginName, std::string& accountName, const std::string& addr, uint16 port)
{
KBEUnordered_map< std::string, COMPONENT_ID >::iterator iter = pending_logins_.find(loginName);
if(iter == pending_logins_.end())
{
ERROR_MSG("Baseappmgr::sendAllocatedBaseappAddr: not found loginapp, pending_logins is error!\n");
return;
} Components::ComponentInfos* cinfos = Components::getSingleton().findComponent(iter->second);
if(cinfos == NULL || cinfos->pChannel == NULL)
{
ERROR_MSG("Baseappmgr::sendAllocatedBaseappAddr: not found loginapp!\n");
return;
} Network::Bundle* pBundleToLoginapp = Network::Bundle::createPoolObject();
(*pBundleToLoginapp).newMessage(LoginappInterface::onLoginAccountQueryBaseappAddrFromBaseappmgr); LoginappInterface::onLoginAccountQueryBaseappAddrFromBaseappmgrArgs4::staticAddToBundle((*pBundleToLoginapp), loginName,
accountName, addr, port); cinfos->pChannel->send(pBundleToLoginapp);
pending_logins_.erase(iter);
}
可以看见,这里返回了消息给loginapp
//-------------------------------------------------------------------------------------
void Loginapp::onLoginAccountQueryBaseappAddrFromBaseappmgr(Network::Channel* pChannel, std::string& loginName,
std::string& accountName, std::string& addr, uint16 port)
{
if(pChannel->isExternal())
return; if(addr.size() == )
{
ERROR_MSG(fmt::format("Loginapp::onLoginAccountQueryBaseappAddrFromBaseappmgr:accountName={}, not found baseapp, Please check the baseappmgr errorlog!\n",
loginName)); std::string datas;
_loginFailed(NULL, loginName, SERVER_ERR_SRV_NO_READY, datas);
} Network::Address address(addr, ntohs(port)); DEBUG_MSG(fmt::format("Loginapp::onLoginAccountQueryBaseappAddrFromBaseappmgr:accountName={0}, addr={1}.\n",
loginName, address.c_str())); // 这里可以不做删除, 仍然使其保留一段时间避免同一时刻同时登录造成意外影响
PendingLoginMgr::PLInfos* infos = pendingLoginMgr_.remove(loginName);
if(infos == NULL)
return; infos->lastProcessTime = timestamp();
Network::Channel* pClientChannel = this->networkInterface().findChannel(infos->addr); if(pClientChannel == NULL)
{
SAFE_RELEASE(infos);
return;
} Network::Bundle* pBundle = Network::Bundle::createPoolObject();
(*pBundle).newMessage(ClientInterface::onLoginSuccessfully);
uint16 fport = ntohs(port);
(*pBundle) << accountName;
(*pBundle) << addr;
(*pBundle) << fport;
(*pBundle).appendBlob(infos->datas);
pClientChannel->send(pBundle); SAFE_RELEASE(infos);
}
我们回到客户端
/*
登录loginapp成功了
*/
public void Client_onLoginSuccessfully(MemoryStream stream)
{
var accountName = stream.readString();
username = accountName;
baseappIP = stream.readString();
baseappPort = stream.readUint16(); Dbg.DEBUG_MSG("KBEngine::Client_onLoginSuccessfully: accountName(" + accountName + "), addr(" +
baseappIP + ":" + baseappPort + "), datas(" + _serverdatas.Length + ")!"); _serverdatas = stream.readBlob();
Person p= (Person)BytesToObject (_serverdatas);
Dbg.DEBUG_MSG("KBEngine::Client_onLoginSuccessfully: p.Age(" + p.Age + "), p.Name(" + p.Name +")!");
login_baseapp(true);
}
客户端向对应的baseapp发送登录请求
/*
登录到服务端,登录到网关(baseapp)
*/
public void login_baseapp(bool noconnect)
{
if(noconnect)
{
Event.fireOut("onLoginBaseapp", new object[]{}); _networkInterface.reset();
_networkInterface = new NetworkInterface();
_networkInterface.connectTo(baseappIP, baseappPort, onConnectTo_baseapp_callback, null);
}
else
{
Bundle bundle = Bundle.createObject();
bundle.newMessage(Message.messages["Baseapp_loginBaseapp"]);
bundle.writeString(username);
bundle.writeString(password);
bundle.send(_networkInterface);
}
}
回到服务器对应的baseapp,查看loginBaseapp方法
//-------------------------------------------------------------------------------------
void Baseapp::loginBaseapp(Network::Channel* pChannel,
std::string& accountName,
std::string& password)
{
accountName = KBEngine::strutil::kbe_trim(accountName);
if(accountName.size() > ACCOUNT_NAME_MAX_LENGTH)
{
ERROR_MSG(fmt::format("Baseapp::loginBaseapp: accountName too big, size={}, limit={}.\n",
accountName.size(), ACCOUNT_NAME_MAX_LENGTH)); return;
} if(password.size() > ACCOUNT_PASSWD_MAX_LENGTH)
{
ERROR_MSG(fmt::format("Baseapp::loginBaseapp: password too big, size={}, limit={}.\n",
password.size(), ACCOUNT_PASSWD_MAX_LENGTH)); return;
} INFO_MSG(fmt::format("Baseapp::loginBaseapp: new user[{0}], channel[{1}].\n",
accountName, pChannel->c_str())); Components::ComponentInfos* dbmgrinfos = Components::getSingleton().getDbmgr();
if(dbmgrinfos == NULL || dbmgrinfos->pChannel == NULL || dbmgrinfos->cid == )
{
loginBaseappFailed(pChannel, accountName, SERVER_ERR_SRV_NO_READY);
return;
} PendingLoginMgr::PLInfos* ptinfos = pendingLoginMgr_.find(accountName);
if(ptinfos == NULL)
{
loginBaseappFailed(pChannel, accountName, SERVER_ERR_ILLEGAL_LOGIN);
return;
}
else if (!ptinfos->addr.isNone() && ptinfos->addr != pChannel->addr())
{
loginBaseappFailed(pChannel, accountName, SERVER_ERR_ILLEGAL_LOGIN);
return;
} if(ptinfos->password != password)
{
loginBaseappFailed(pChannel, accountName, SERVER_ERR_PASSWORD);
return;
} if((ptinfos->flags & ACCOUNT_FLAG_LOCK) > )
{
loginBaseappFailed(pChannel, accountName, SERVER_ERR_ACCOUNT_LOCK);
return;
} if((ptinfos->flags & ACCOUNT_FLAG_NOT_ACTIVATED) > )
{
loginBaseappFailed(pChannel, accountName, SERVER_ERR_ACCOUNT_NOT_ACTIVATED);
return;
} if(ptinfos->deadline > && ::time(NULL) - ptinfos->deadline <= )
{
loginBaseappFailed(pChannel, accountName, SERVER_ERR_ACCOUNT_DEADLINE);
return;
} if(idClient_.size() == )
{
ERROR_MSG("Baseapp::loginBaseapp: idClient size is 0.\n");
loginBaseappFailed(pChannel, accountName, SERVER_ERR_SRV_NO_READY);
return;
} // 如果entityID大于0则说明此entity是存活状态登录
if(ptinfos->entityID > )
{
INFO_MSG(fmt::format("Baseapp::loginBaseapp: user[{}] has entity({}).\n",
accountName.c_str(), ptinfos->entityID)); Proxy* base = static_cast<Proxy*>(findEntity(ptinfos->entityID));
if(base == NULL || base->isDestroyed())
{
loginBaseappFailed(pChannel, accountName, SERVER_ERR_BUSY);
return;
} pendingLoginMgr_.removeNextTick(accountName); // 防止在onLogOnAttempt中销毁了
Py_INCREF(base); // 通知脚本异常登录请求有脚本决定是否允许这个通道强制登录
int32 ret = base->onLogOnAttempt(pChannel->addr().ipAsString(),
ntohs(pChannel->addr().port), password.c_str()); if (base->isDestroyed())
{
Py_DECREF(base); loginBaseappFailed(pChannel, accountName, SERVER_ERR_OP_FAILED);
return;
} switch(ret)
{
case LOG_ON_ACCEPT:
if(base->clientMailbox() != NULL)
{
// 通告在别处登录
Network::Channel* pOldClientChannel = base->clientMailbox()->getChannel();
if(pOldClientChannel != NULL)
{
INFO_MSG(fmt::format("Baseapp::loginBaseapp: script LOG_ON_ACCEPT. oldClientChannel={}\n",
pOldClientChannel->c_str())); kickChannel(pOldClientChannel, SERVER_ERR_ACCOUNT_LOGIN_ANOTHER);
}
else
{
INFO_MSG("Baseapp::loginBaseapp: script LOG_ON_ACCEPT.\n");
} base->clientMailbox()->addr(pChannel->addr());
base->addr(pChannel->addr());
base->setClientType(ptinfos->ctype);
base->setClientDatas(ptinfos->datas);
createClientProxies(base, true);
base->onGetWitness();
}
else
{
// 创建entity的客户端mailbox
EntityMailbox* entityClientMailbox = new EntityMailbox(base->pScriptModule(),
&pChannel->addr(), , base->id(), MAILBOX_TYPE_CLIENT); base->clientMailbox(entityClientMailbox);
base->addr(pChannel->addr());
base->setClientType(ptinfos->ctype);
base->setClientDatas(ptinfos->datas); // 将通道代理的关系与该entity绑定, 在后面通信中可提供身份合法性识别
entityClientMailbox->getChannel()->proxyID(base->id());
createClientProxies(base, true);
base->onGetWitness();
}
break;
case LOG_ON_WAIT_FOR_DESTROY:
default:
INFO_MSG("Baseapp::loginBaseapp: script LOG_ON_REJECT.\n");
loginBaseappFailed(pChannel, accountName, SERVER_ERR_ACCOUNT_IS_ONLINE);
Py_DECREF(base);
return;
}; Py_DECREF(base);
}
else
{
Network::Bundle* pBundle = Network::Bundle::createPoolObject();
(*pBundle).newMessage(DbmgrInterface::queryAccount); ENTITY_ID entityID = idClient_.alloc();
KBE_ASSERT(entityID > ); DbmgrInterface::queryAccountArgs7::staticAddToBundle((*pBundle), accountName, password, g_componentID,
entityID, ptinfos->entityDBID, pChannel->addr().ip, pChannel->addr().port); dbmgrinfos->pChannel->send(pBundle);
} // 记录客户端地址
ptinfos->addr = pChannel->addr();
}
这里最重要的事情是,
1、如果存在实体,创立mailbox,并且绑定mailbox给对应的实体
2、如果不存在实体,那么调用DbmgrInterface::queryAccount
void Dbmgr::queryAccount(Network::Channel* pChannel,
std::string& accountName,
std::string& password,
COMPONENT_ID componentID,
ENTITY_ID entityID,
DBID entityDBID,
uint32 ip,
uint16 port)
{
if(accountName.size() == )
{
ERROR_MSG("Dbmgr::queryAccount: accountName is empty.\n");
return;
} Buffered_DBTasks* pBuffered_DBTasks =
findBufferedDBTask(Dbmgr::getSingleton().selectAccountDBInterfaceName(accountName)); if (!pBuffered_DBTasks)
{
ERROR_MSG(fmt::format("Dbmgr::queryAccount: not found dbInterface({})!\n",
Dbmgr::getSingleton().selectAccountDBInterfaceName(accountName)));
return;
} pBuffered_DBTasks->addTask(new DBTaskQueryAccount(pChannel->addr(), accountName, password,
componentID, entityID, entityDBID, ip, port)); numQueryEntity_++;
}
我们来看下DBTaskQueryAccount的最关键的两个方法
//-------------------------------------------------------------------------------------
bool DBTaskQueryAccount::db_thread_process()
{
if(accountName_.size() == )
{
error_ = "accountName_ is NULL";
return false;
} EntityTables& entityTables = EntityTables::findByInterfaceName(pdbi_->name());
KBEAccountTable* pTable = static_cast<KBEAccountTable*>(entityTables.findKBETable("kbe_accountinfos"));
KBE_ASSERT(pTable); ACCOUNT_INFOS info;
info.name = "";
info.password = "";
info.dbid = dbid_; if(dbid_ == )
{
if(!pTable->queryAccount(pdbi_, accountName_, info))
{
error_ = "pTable->queryAccount() is failed!"; if(pdbi_->getlasterror() > )
{
error_ += pdbi_->getstrerror();
} return false;
} if(info.dbid == )
{
error_ = "dbid is 0";
return false;
} if(info.dbid == || info.flags != ACCOUNT_FLAG_NORMAL)
{
error_ = "flags != ACCOUNT_FLAG_NORMAL";
flags_ = info.flags;
return false;
} if (kbe_stricmp(info.password.c_str(), KBE_MD5::getDigest(password_.data(), (int)password_.length()).c_str()) != )
{
error_ = "password is error";
return false;
}
} ScriptDefModule* pModule = EntityDef::findScriptModule(DBUtil::accountScriptName());
success_ = entityTables.queryEntity(pdbi_, info.dbid, &s_, pModule); if(!success_ && pdbi_->getlasterror() > )
{
error_ += "queryEntity: ";
error_ += pdbi_->getstrerror();
} dbid_ = info.dbid; if(!success_)
return false; success_ = false; // 先写log, 如果写失败则可能这个entity已经在线
KBEEntityLogTable* pELTable = static_cast<KBEEntityLogTable*>
(entityTables.findKBETable("kbe_entitylog"));
KBE_ASSERT(pELTable); success_ = pELTable->logEntity(pdbi_, inet_ntoa((struct in_addr&)ip_), port_, dbid_,
componentID_, entityID_, pModule->getUType()); if(!success_ && pdbi_->getlasterror() > )
{
error_ += "logEntity: ";
error_ += pdbi_->getstrerror();
} flags_ = info.flags;
deadline_ = info.deadline; return false;
} //-------------------------------------------------------------------------------------
thread::TPTask::TPTaskState DBTaskQueryAccount::presentMainThread()
{
DEBUG_MSG(fmt::format("Dbmgr::queryAccount: {}, success={}, flags={}, deadline={}.\n",
accountName_.c_str(), success_, flags_, deadline_)); Network::Bundle* pBundle = Network::Bundle::createPoolObject();
(*pBundle).newMessage(BaseappInterface::onQueryAccountCBFromDbmgr);
(*pBundle) << pdbi_->dbIndex();
(*pBundle) << accountName_;
(*pBundle) << password_;
(*pBundle) << dbid_;
(*pBundle) << success_;
(*pBundle) << entityID_;
(*pBundle) << flags_;
(*pBundle) << deadline_; if(success_)
{
pBundle->append(s_);
}
else
{
(*pBundle) << error_;
} if(!this->send(pBundle))
{
ERROR_MSG(fmt::format("DBTaskQueryAccount::presentMainThread: channel({}) not found.\n", addr_.c_str()));
Network::Bundle::reclaimPoolObject(pBundle);
} return EntityDBTask::presentMainThread();
}
比对过密码的MD5等数据后,通过onQueryAccountCBFromDbmgr消息返回对应的baseapp,
//-------------------------------------------------------------------------------------
void Baseapp::onQueryAccountCBFromDbmgr(Network::Channel* pChannel, KBEngine::MemoryStream& s)
{
if(pChannel->isExternal())
return; std::string accountName;
std::string password;
bool success = false;
DBID dbid;
ENTITY_ID entityID;
uint32 flags;
uint64 deadline;
uint16 dbInterfaceIndex; s >> dbInterfaceIndex >> accountName >> password >> dbid >> success >> entityID >> flags >> deadline; PendingLoginMgr::PLInfos* ptinfos = pendingLoginMgr_.remove(accountName);
if(ptinfos == NULL)
{
ERROR_MSG(fmt::format("Baseapp::onQueryAccountCBFromDbmgr: PendingLoginMgr not found({})\n",
accountName.c_str())); s.done();
return;
} Proxy* base = static_cast<Proxy*>(createEntity(g_serverConfig.getDBMgr().dbAccountEntityScriptType,
NULL, false, entityID)); Network::Channel* pClientChannel = this->networkInterface().findChannel(ptinfos->addr); if(!base)
{
ERROR_MSG(fmt::format("Baseapp::onQueryAccountCBFromDbmgr: create {} is failed! error(base == NULL)\n",
accountName.c_str())); s.done(); loginBaseappFailed(pClientChannel, accountName, SERVER_ERR_SRV_NO_READY);
return;
} if(!success)
{
std::string error;
s >> error;
ERROR_MSG(fmt::format("Baseapp::onQueryAccountCBFromDbmgr: query {} is failed! error({})\n",
accountName.c_str(), error)); s.done(); loginBaseappFailed(pClientChannel, accountName, SERVER_ERR_SRV_NO_READY); this->destroyEntity(base->id(), true);
return;
} KBE_ASSERT(base != NULL);
base->hasDB(true);
base->dbid(dbInterfaceIndex, dbid);
base->setClientType(ptinfos->ctype);
base->setClientDatas(ptinfos->datas); PyObject* pyDict = createCellDataDictFromPersistentStream(s, g_serverConfig.getDBMgr().dbAccountEntityScriptType); PyObject* py__ACCOUNT_NAME__ = PyUnicode_FromString(accountName.c_str());
PyDict_SetItemString(pyDict, "__ACCOUNT_NAME__", py__ACCOUNT_NAME__);
Py_DECREF(py__ACCOUNT_NAME__); PyObject* py__ACCOUNT_PASSWD__ = PyUnicode_FromString(KBE_MD5::getDigest(password.data(), (int)password.length()).c_str());
PyDict_SetItemString(pyDict, "__ACCOUNT_PASSWORD__", py__ACCOUNT_PASSWD__);
Py_DECREF(py__ACCOUNT_PASSWD__); Py_INCREF(base);
base->initializeEntity(pyDict);
Py_DECREF(pyDict); if(pClientChannel != NULL)
{
// 创建entity的客户端mailbox
EntityMailbox* entityClientMailbox = new EntityMailbox(base->pScriptModule(),
&pClientChannel->addr(), , base->id(), MAILBOX_TYPE_CLIENT); base->clientMailbox(entityClientMailbox);
base->addr(pClientChannel->addr()); createClientProxies(base); /*
Network::Bundle* pBundle = Network::Bundle::createPoolObject();
(*pBundle).newMessage(DbmgrInterface::onAccountOnline); DbmgrInterface::onAccountOnlineArgs3::staticAddToBundle((*pBundle), accountName,
componentID_, base->id()); pChannel->send(pBundle);
*/
} INFO_MSG(fmt::format("Baseapp::onQueryAccountCBFromDbmgr: user={}, uuid={}, entityID={}, flags={}, deadline={}.\n",
accountName, base->rndUUID(), base->id(), flags, deadline)); SAFE_RELEASE(ptinfos);
Py_DECREF(base);
}
通过
Proxy* base = static_cast<Proxy*>(createEntity(g_serverConfig.getDBMgr().dbAccountEntityScriptType,
NULL, false, entityID));
创建了对应的实体,并且创建对应的mailbox绑定上去,值得注意的是createClientProxies(base);这个函数,这个函数会将实体创建完毕的消息传回给客户端,并告知客户端此通道的存在
此后脚本层的消息传递,就直接通过这个通道进行
//-------------------------------------------------------------------------------------
bool Baseapp::createClientProxies(Proxy* base, bool reload)
{
Py_INCREF(base); // 将通道代理的关系与该entity绑定, 在后面通信中可提供身份合法性识别
Network::Channel* pChannel = base->clientMailbox()->getChannel();
pChannel->proxyID(base->id());
base->addr(pChannel->addr()); // 重新生成一个ID
if(reload)
base->rndUUID(genUUID64()); // 一些数据必须在实体创建后立即访问
base->initClientBasePropertys(); // 让客户端知道已经创建了proxices, 并初始化一部分属性
Network::Bundle* pBundle = Network::Bundle::createPoolObject();
(*pBundle).newMessage(ClientInterface::onCreatedProxies);
(*pBundle) << base->rndUUID();
(*pBundle) << base->id();
(*pBundle) << base->ob_type->tp_name;
//base->clientMailbox()->postMail((*pBundle));
base->sendToClient(ClientInterface::onCreatedProxies, pBundle); // 本应该由客户端告知已经创建好entity后调用这个接口。
//if(!reload)
base->onEntitiesEnabled();
Py_DECREF(base);
return true;
}
在这里一方面告知客户端onCreatedProxies消息,一方面进行脚本的onEntitiesEnabled消息的回调
我们回到unity端,阅读对应的代码
/*
服务端通知创建一个角色
*/
public void Client_onCreatedProxies(UInt64 rndUUID, Int32 eid, string entityType)
{
Dbg.DEBUG_MSG("KBEngine::Client_onCreatedProxies: eid(" + eid + "), entityType(" + entityType + ")!"); entity_uuid = rndUUID;
entity_id = eid;
entity_type = entityType; if(!this.entities.ContainsKey(eid))
{
ScriptModule module = null;
if(!EntityDef.moduledefs.TryGetValue(entityType, out module))
{
Dbg.ERROR_MSG("KBEngine::Client_onCreatedProxies: not found module(" + entityType + ")!");
return;
} Type runclass = module.script;
if(runclass == null)
return; Entity entity = (Entity)Activator.CreateInstance(runclass);
entity.id = eid;
entity.className = entityType; entity.baseMailbox = new Mailbox();
entity.baseMailbox.id = eid;
entity.baseMailbox.className = entityType;
entity.baseMailbox.type = Mailbox.MAILBOX_TYPE.MAILBOX_TYPE_BASE; entities[eid] = entity; MemoryStream entityMessage = null;
_bufferedCreateEntityMessage.TryGetValue(eid, out entityMessage); if(entityMessage != null)
{
Client_onUpdatePropertys(entityMessage);
_bufferedCreateEntityMessage.Remove(eid);
entityMessage.reclaimObject();
} entity.__init__();
entity.inited = true; if(_args.isOnInitCallPropertysSetMethods)
entity.callPropertysSetMethods();
}
else
{
MemoryStream entityMessage = null;
_bufferedCreateEntityMessage.TryGetValue(eid, out entityMessage); if(entityMessage != null)
{
Client_onUpdatePropertys(entityMessage);
_bufferedCreateEntityMessage.Remove(eid);
entityMessage.reclaimObject();
}
}
}
客户端也进行了mailbox的绑定,并且回调了对应entity的init方法,account的entity是account.cs,点开看下
public override void __init__()
{
Event.fireOut("onLoginSuccessfully", new object[]{KBEngineApp.app.entity_uuid, id, this});
baseCall("reqAvatarList", new object[]);
}
这里开始,请求角色列表,放到下一篇文章吧。
我这篇文章拷贝黏贴了大量的代码,C++的代码十分臃肿,KBE已经进行了大量的封装,使其更接近逻辑的本质。
我是青岛远硕信息科技发展有限公司的Peter,如果转载的话,请保留这段文字。
KBEngine warring项目源码阅读(二) 登录和baseapp的负载均衡的更多相关文章
- KBEngine warring项目源码阅读(一) 项目简介和注册
首先介绍下warring项目,是kbe自带的一个演示示例,大部分人了解kbe引擎也是从warring项目开始的. 项目地址:https://github.com/kbengine/kbengine_u ...
- KBEngine warring项目源码阅读(三) 实体文件与Account处理
上一篇开始,我们就提到了一个概念,并且进行了初步的运用,这个概念就是实体. KBE中的实体是一个很重要的概念,可以说,有了实体就有了一切. 我们首先接着上一章的内容,来看Account.def对应的实 ...
- [源码解析] 并行分布式任务队列 Celery 之 负载均衡
[源码解析] 并行分布式任务队列 Celery 之 负载均衡 目录 [源码解析] 并行分布式任务队列 Celery 之 负载均衡 0x00 摘要 0x01 负载均衡 1.1 哪几个 queue 1.1 ...
- fw: 专访许鹏:谈C程序员修养及大型项目源码阅读与学习
C家最近也有一篇关于如何阅读大型c项目源代码的文章,学习..融合.. -------------------- ref:http://www.csdn.net/article/2014-06-05 ...
- Spring 源码阅读 二
程序入口: 接着上一篇博客中看完了在AnnotationConfigApplicationContext的构造函数中的register(annotatedClasses);将我们传递进来的主配置类添加 ...
- xxl-job源码阅读二(服务端)
1.源码入口 xxl-job-admin是一个简单的springboot工程,简单翻看源码,可以很快发现XxlJobAdminConfig入口. @Override public void after ...
- 【一起学源码-微服务】Feign 源码三:Feign结合Ribbon实现负载均衡的原理分析
前言 前情回顾 上一讲我们已经知道了Feign的工作原理其实是在项目启动的时候,通过JDK动态代理为每个FeignClinent生成一个动态代理. 动态代理的数据结构是:ReflectiveFeign ...
- SparkConf加载与SparkContext创建(源码阅读二)
紧接着昨天,我们继续开搞了啊.. 1.下面,开始创建BroadcastManager,就是传说中的广播变量管理器.BroadcastManager用于将配置信息和序列化后的RDD.Job以及Shuff ...
- JDK源码阅读(二) AbstractList
package java.util; public abstract class AbstractList<E> extends AbstractCollection<E> i ...
随机推荐
- “在注释中遇到意外的文件结束”--记一个令人崩溃的bug
下午写程序,写的好好的,突然报错"在注释中遇到意外的文件结束". 下面是官方给出的错误原因是缺少注释终结器 (* /). // C1071.cpp int main() { } / ...
- 使用JFileChooser保存文件
--------------------siwuxie095 工程名:TestFileChooser 包名:com.siwuxie095 ...
- Centos 6.5 hadoop 2.2.0 全分布式安装
hadoop 2.2.0 cluster setup 环境: 操作系统:Centos 6.5 jdk:jdk1.7.0_51 hadoop版本:2.2.0 hostname ip master ...
- solr搜索应用
非票商品搜索,为了不模糊查询影响数据库的性能,搭建了solr搜索应用,php从solr读取数据
- 阶段4-独挡一面\项目-基于视频压缩的实时监控系统\Sprint1-基于Epoll架构的采集端程序框架设计\第2课-基于Epoll的采集端程序框架设计
回顾之前的整个程序架构 把epoll机制应用到这个架构上去 下面主要去分析我们的系统中有没有需要等待的事件,先看看采集子系统 在采集子系统当中,摄像头有数据,摄像头采集到图像数据可以作为一个等待事件. ...
- 拦截导弹 (NYOJ—79) 最长字串问题 (NYOJ—17)
这是到动态规划的题目,属于有顺序的0 1 背包问题: 代码: #include<stdio.h> #include<string.h> ][]; //d[i][j] ]; in ...
- C#交换两个数字
- vue中computed与methods的异同
在vue.js中,有methods和computed两种方式来动态当作方法来用的 如下: 两种方式在这种情况下的结果是一样的 写法上的区别是computed计算属性的方式在用属性时不用加(),而met ...
- Codeforces Round#522 Div2E(思维,背包,组合数学)
#include<bits/stdc++.h>using namespace std;int a[107];int b[10007][107];int c[107][107];int ma ...
- Mybatis学习笔记之一——牛刀小试
1.Mybaits核心对象SqlSession的作用: (1)向SQL语句传入参数: (2)执行SQl语句: (3)获取执行SQL语句的结果: (4)事务的控制: 2.核心配置文件(Configrat ...