首先介紹下warring項目,是kbe自帶的一個演示示例,大部分人了解kbe引擎也是從warring項目開始的。
項目地址:https://github.com/kbengine/kbengine_unity3d_warring
項目截圖:





項目的下載和安裝不再多說,現在開始進入代碼講解階段:
注冊:
流程圖:


可以看到控件綁定代碼為reg_ok,點進去
1 void reg_ok() 2 { 3 log_label.obj.text = "請求連接服務器..."; 4 log_label.obj.color = UnityEngine.Color.green; 5 6 if(reg_username.input.text == "" || reg_username.input.text.Length > 30) 7 { 8 log_label.obj.color = UnityEngine.Color.red; 9 log_label.obj.text = "用戶名或者郵箱地址不合法, 最大長度限制30個字符。"; 10 Common.WARNING_MSG("ui::reg_ok: invalid username!"); 11 return; 12 } 13 14 if(reg_password.input.text.Length < 6 || reg_password.input.text.Length > 16) 15 { 16 log_label.obj.color = UnityEngine.Color.red; 17 log_label.obj.text = "密碼不合法, 長度限制在6~16位之間。"; 18 Common.WARNING_MSG("ui::reg_ok: invalid reg_password!"); 19 return; 20 } 21 22 if(reg_password.input.text != reg_passwordok.input.text) 23 { 24 log_label.obj.color = UnityEngine.Color.red; 25 log_label.obj.text = "二次輸入密碼不匹配。"; 26 Common.WARNING_MSG("ui::reg_ok: reg_password != reg_passwordok!"); 27 return; 28 } 29 30 KBEngine.Event.fireIn("createAccount", reg_username.input.text, reg_passwordok.input.text, System.Text.Encoding.UTF8.GetBytes("kbengine_unity_warring")); 31 log_label.obj.text = "連接成功,等待處理請稍后..."; 32 }
可以看到接下來是fireIn("createAccount",xxxx,...)
這里需要講解一下客戶端的fireIn和fireOut是怎么一回事,fireIn是指u3d腳本層觸發一個事件給kbe插件執行,fireOut是是插件向u3d腳本層觸發的事件,總之是從unity到kbe插件的一個交互過程。既然是插件層層,那么我們打開KBEngine.cs去找對應的registerIn,可以找到下面的代碼
1 void installEvents() 2 { 3 Event.registerIn("createAccount", this, "createAccount"); 4 Event.registerIn("login", this, "login"); 5 Event.registerIn("reloginBaseapp", this, "reloginBaseapp"); 6 Event.registerIn("resetPassword", this, "resetPassword"); 7 Event.registerIn("bindAccountEmail", this, "bindAccountEmail"); 8 Event.registerIn("newPassword", this, "newPassword"); 9 10 // 內部事件 11 Event.registerIn("_closeNetwork", this, "_closeNetwork"); 12 }
然后在同一文件的第727行,找到對應的消息,可以看到下一步是調用的createAccount_loginapp(false)函數

點開進去
1 /* 2 創建賬號,通過loginapp 3 */ 4 public void createAccount_loginapp(bool noconnect) 5 { 6 if(noconnect) 7 { 8 reset(); 9 _networkInterface.connectTo(_args.ip, _args.port, onConnectTo_createAccount_callback, null); 10 } 11 else 12 { 13 Bundle bundle = Bundle.createObject(); 14 bundle.newMessage(Message.messages["Loginapp_reqCreateAccount"]); 15 bundle.writeString(username); 16 bundle.writeString(password); 17 //string imei = 'AET89766-124'; 18 //bundle.writeString(imei); 19 bundle.writeBlob(KBEngineApp.app._clientdatas); 20 bundle.send(_networkInterface); 21 } 22 }
可以看到這里開始給后端發了一個消息,消息關鍵字是Loginapp_reqCreateAccount。我們打開kbe的C++部分源碼
在loginapp項目中,找到loginapp.cpp的reqCreateAccount方法,為什么要找這個方法,因為在代碼底層識別的時候將關鍵字變為了前半段代表的節點名,后半段代表消息。
1 //------------------------------------------------------------------------------------- 2 void Loginapp::reqCreateAccount(Network::Channel* pChannel, MemoryStream& s) 3 { 4 std::string accountName, password, datas; 5 6 s >> accountName >> password; 7 s.readBlob(datas); 8 9 if(!_createAccount(pChannel, accountName, password, datas, ACCOUNT_TYPE(g_serverConfig.getLoginApp().account_type))) 10 return; 11 }
點開_createAccount
1 //------------------------------------------------------------------------------------- 2 bool Loginapp::_createAccount(Network::Channel* pChannel, std::string& accountName, 3 std::string& password, std::string& datas, ACCOUNT_TYPE type) 4 { 5 AUTO_SCOPED_PROFILE("createAccount"); 6 7 ACCOUNT_TYPE oldType = type; 8 9 if(!g_kbeSrvConfig.getDBMgr().account_registration_enable) 10 { 11 ERROR_MSG(fmt::format("Loginapp::_createAccount({}): not available! modify kbengine[_defs].xml->dbmgr->account_registration.\n", 12 accountName)); 13 14 std::string retdatas = ""; 15 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 16 (*pBundle).newMessage(ClientInterface::onCreateAccountResult); 17 SERVER_ERROR_CODE retcode = SERVER_ERR_ACCOUNT_REGISTER_NOT_AVAILABLE; 18 (*pBundle) << retcode; 19 (*pBundle).appendBlob(retdatas); 20 pChannel->send(pBundle); 21 return false; 22 } 23 24 accountName = KBEngine::strutil::kbe_trim(accountName); 25 password = KBEngine::strutil::kbe_trim(password); 26 27 if(accountName.size() > ACCOUNT_NAME_MAX_LENGTH) 28 { 29 ERROR_MSG(fmt::format("Loginapp::_createAccount: accountName too big, size={}, limit={}.\n", 30 accountName.size(), ACCOUNT_NAME_MAX_LENGTH)); 31 32 return false; 33 } 34 35 if(password.size() > ACCOUNT_PASSWD_MAX_LENGTH) 36 { 37 ERROR_MSG(fmt::format("Loginapp::_createAccount: password too big, size={}, limit={}.\n", 38 password.size(), ACCOUNT_PASSWD_MAX_LENGTH)); 39 40 return false; 41 } 42 43 if(datas.size() > ACCOUNT_DATA_MAX_LENGTH) 44 { 45 ERROR_MSG(fmt::format("Loginapp::_createAccount: bindatas too big, size={}, limit={}.\n", 46 datas.size(), ACCOUNT_DATA_MAX_LENGTH)); 47 48 return false; 49 } 50 51 std::string retdatas = ""; 52 if(shuttingdown_ != SHUTDOWN_STATE_STOP) 53 { 54 WARNING_MSG(fmt::format("Loginapp::_createAccount: shutting down, create {} failed!\n", accountName)); 55 56 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 57 (*pBundle).newMessage(ClientInterface::onCreateAccountResult); 58 SERVER_ERROR_CODE retcode = SERVER_ERR_IN_SHUTTINGDOWN; 59 (*pBundle) << retcode; 60 (*pBundle).appendBlob(retdatas); 61 pChannel->send(pBundle); 62 return false; 63 } 64 65 PendingLoginMgr::PLInfos* ptinfos = pendingCreateMgr_.find(const_cast<std::string&>(accountName)); 66 if(ptinfos != NULL) 67 { 68 WARNING_MSG(fmt::format("Loginapp::_createAccount: pendingCreateMgr has {}, request create failed!\n", 69 accountName)); 70 71 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 72 (*pBundle).newMessage(ClientInterface::onCreateAccountResult); 73 SERVER_ERROR_CODE retcode = SERVER_ERR_BUSY; 74 (*pBundle) << retcode; 75 (*pBundle).appendBlob(retdatas); 76 pChannel->send(pBundle); 77 return false; 78 } 79 80 { 81 // 把請求交由腳本處理 82 SERVER_ERROR_CODE retcode = SERVER_SUCCESS; 83 SCOPED_PROFILE(SCRIPTCALL_PROFILE); 84 85 PyObject* pyResult = PyObject_CallMethod(getEntryScript().get(), 86 const_cast<char*>("onRequestCreateAccount"), 87 const_cast<char*>("ssy#"), 88 accountName.c_str(), 89 password.c_str(), 90 datas.c_str(), datas.length()); 91 92 if(pyResult != NULL) 93 { 94 if(PySequence_Check(pyResult) && PySequence_Size(pyResult) == 4) 95 { 96 char* sname; 97 char* spassword; 98 char *extraDatas; 99 Py_ssize_t extraDatas_size = 0; 100 101 if(PyArg_ParseTuple(pyResult, "H|s|s|y#", &retcode, &sname, &spassword, &extraDatas, &extraDatas_size) == -1) 102 { 103 ERROR_MSG(fmt::format("Loginapp::_createAccount: {}.onReuqestLogin, Return value error! accountName={}\n", 104 g_kbeSrvConfig.getLoginApp().entryScriptFile, accountName)); 105 106 retcode = SERVER_ERR_OP_FAILED; 107 } 108 else 109 { 110 accountName = sname; 111 password = spassword; 112 113 if (extraDatas && extraDatas_size > 0) 114 datas.assign(extraDatas, extraDatas_size); 115 else 116 SCRIPT_ERROR_CHECK(); 117 } 118 } 119 else 120 { 121 ERROR_MSG(fmt::format("Loginapp::_createAccount: {}.onReuqestLogin, Return value error, must be errorcode or tuple! accountName={}\n", 122 g_kbeSrvConfig.getLoginApp().entryScriptFile, accountName)); 123 124 retcode = SERVER_ERR_OP_FAILED; 125 } 126 127 Py_DECREF(pyResult); 128 } 129 else 130 { 131 SCRIPT_ERROR_CHECK(); 132 retcode = SERVER_ERR_OP_FAILED; 133 } 134 135 if(retcode != SERVER_SUCCESS) 136 { 137 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 138 (*pBundle).newMessage(ClientInterface::onCreateAccountResult); 139 (*pBundle) << retcode; 140 (*pBundle).appendBlob(retdatas); 141 pChannel->send(pBundle); 142 return false; 143 } 144 else 145 { 146 if(accountName.size() == 0) 147 { 148 ERROR_MSG(fmt::format("Loginapp::_createAccount: accountName is empty!\n")); 149 150 retcode = SERVER_ERR_NAME; 151 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 152 (*pBundle).newMessage(ClientInterface::onCreateAccountResult); 153 (*pBundle) << retcode; 154 (*pBundle).appendBlob(retdatas); 155 pChannel->send(pBundle); 156 return false; 157 } 158 } 159 } 160 161 if(type == ACCOUNT_TYPE_SMART) 162 { 163 if (email_isvalid(accountName.c_str())) 164 { 165 type = ACCOUNT_TYPE_MAIL; 166 } 167 else 168 { 169 if(!validName(accountName)) 170 { 171 ERROR_MSG(fmt::format("Loginapp::_createAccount: invalid accountName({})\n", 172 accountName)); 173 174 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 175 (*pBundle).newMessage(ClientInterface::onCreateAccountResult); 176 SERVER_ERROR_CODE retcode = SERVER_ERR_NAME; 177 (*pBundle) << retcode; 178 (*pBundle).appendBlob(retdatas); 179 pChannel->send(pBundle); 180 return false; 181 } 182 183 type = ACCOUNT_TYPE_NORMAL; 184 } 185 } 186 else if(type == ACCOUNT_TYPE_NORMAL) 187 { 188 if(!validName(accountName)) 189 { 190 ERROR_MSG(fmt::format("Loginapp::_createAccount: invalid accountName({})\n", 191 accountName)); 192 193 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 194 (*pBundle).newMessage(ClientInterface::onCreateAccountResult); 195 SERVER_ERROR_CODE retcode = SERVER_ERR_NAME; 196 (*pBundle) << retcode; 197 (*pBundle).appendBlob(retdatas); 198 pChannel->send(pBundle); 199 return false; 200 } 201 } 202 else if (!email_isvalid(accountName.c_str())) 203 { 204 /* 205 std::string user_name, domain_name; 206 user_name = regex_replace(accountName, _g_mail_pattern, std::string("$1") ); 207 domain_name = regex_replace(accountName, _g_mail_pattern, std::string("$2") ); 208 */ 209 WARNING_MSG(fmt::format("Loginapp::_createAccount: invalid mail={}\n", 210 accountName)); 211 212 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 213 (*pBundle).newMessage(ClientInterface::onCreateAccountResult); 214 SERVER_ERROR_CODE retcode = SERVER_ERR_NAME_MAIL; 215 (*pBundle) << retcode; 216 (*pBundle).appendBlob(retdatas); 217 pChannel->send(pBundle); 218 return false; 219 } 220 221 DEBUG_MSG(fmt::format("Loginapp::_createAccount: accountName={}, passwordsize={}, type={}, oldType={}.\n", 222 accountName.c_str(), password.size(), type, oldType)); 223 224 ptinfos = new PendingLoginMgr::PLInfos; 225 ptinfos->accountName = accountName; 226 ptinfos->password = password; 227 ptinfos->datas = datas; 228 ptinfos->addr = pChannel->addr(); 229 pendingCreateMgr_.add(ptinfos); 230 231 Components::COMPONENTS& cts = Components::getSingleton().getComponents(DBMGR_TYPE); 232 Components::ComponentInfos* dbmgrinfos = NULL; 233 234 if(cts.size() > 0) 235 dbmgrinfos = &(*cts.begin()); 236 237 if(dbmgrinfos == NULL || dbmgrinfos->pChannel == NULL || dbmgrinfos->cid == 0) 238 { 239 ERROR_MSG(fmt::format("Loginapp::_createAccount: create({}), not found dbmgr!\n", 240 accountName)); 241 242 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 243 (*pBundle).newMessage(ClientInterface::onCreateAccountResult); 244 SERVER_ERROR_CODE retcode = SERVER_ERR_SRV_NO_READY; 245 (*pBundle) << retcode; 246 (*pBundle).appendBlob(retdatas); 247 pChannel->send(pBundle); 248 return false; 249 } 250 251 pChannel->extra(accountName); 252 253 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 254 (*pBundle).newMessage(DbmgrInterface::reqCreateAccount); 255 uint8 uatype = uint8(type); 256 (*pBundle) << accountName << password << uatype; 257 (*pBundle).appendBlob(datas); 258 dbmgrinfos->pChannel->send(pBundle); 259 return true; 260 }
可以看到,進行了一堆繁瑣的驗證以后,最后將解析出來的用戶名密碼等其他數據
我們打開dbmgr,找到reqCreateAccount函數
1 //------------------------------------------------------------------------------------- 2 void Dbmgr::reqCreateAccount(Network::Channel* pChannel, KBEngine::MemoryStream& s) 3 { 4 std::string registerName, password, datas; 5 uint8 uatype = 0; 6 7 s >> registerName >> password >> uatype; 8 s.readBlob(datas); 9 10 if(registerName.size() == 0) 11 { 12 ERROR_MSG("Dbmgr::reqCreateAccount: registerName is empty.\n"); 13 return; 14 } 15 16 pInterfacesAccountHandler_->createAccount(pChannel, registerName, password, datas, ACCOUNT_TYPE(uatype)); 17 numCreatedAccount_++; 18 }
然后點開createAccount,因為一般情況下配置文件會填寫host和port,所以我們進入InterfacesHandler_Dbmgr::createAccount的方法查看
1 //------------------------------------------------------------------------------------- 2 bool InterfacesHandler_Dbmgr::createAccount(Network::Channel* pChannel, std::string& registerName, 3 std::string& password, std::string& datas, ACCOUNT_TYPE uatype) 4 { 5 std::string dbInterfaceName = Dbmgr::getSingleton().selectAccountDBInterfaceName(registerName); 6 7 thread::ThreadPool* pThreadPool = DBUtil::pThreadPool(dbInterfaceName); 8 if (!pThreadPool) 9 { 10 ERROR_MSG(fmt::format("InterfacesHandler_Dbmgr::createAccount: not found dbInterface({})!\n", 11 dbInterfaceName)); 12 13 return false; 14 } 15 16 // 如果是email,先查詢賬號是否存在然后將其登記入庫 17 if(uatype == ACCOUNT_TYPE_MAIL) 18 { 19 pThreadPool->addTask(new DBTaskCreateMailAccount(pChannel->addr(), 20 registerName, registerName, password, datas, datas)); 21 22 return true; 23 } 24 25 pThreadPool->addTask(new DBTaskCreateAccount(pChannel->addr(), 26 registerName, registerName, password, datas, datas)); 27 28 return true; 29 }
可以看到,這里是用了一個異步任務隊列的形式,進行的數據庫寫入,點開DBTaskCreateAccount,事實上郵件賬號的原理是一樣的
我們簡單的看下DBTaskCreateAccount這個類,頭文件
1 /** 2 創建一個賬號到數據庫 3 */ 4 class DBTaskCreateAccount : public DBTask 5 { 6 public: 7 DBTaskCreateAccount(const Network::Address& addr, std::string& registerName, std::string& accountName, 8 std::string& password, std::string& postdatas, std::string& getdatas); 9 virtual ~DBTaskCreateAccount(); 10 virtual bool db_thread_process(); 11 virtual thread::TPTask::TPTaskState presentMainThread(); 12 13 static bool writeAccount(DBInterface* pdbi, const std::string& accountName, 14 const std::string& passwd, const std::string& datas, ACCOUNT_INFOS& info); 15 16 protected: 17 std::string registerName_; 18 std::string accountName_; 19 std::string password_; 20 std::string postdatas_, getdatas_; 21 bool success_; 22 23 };
CPP文件
1 //------------------------------------------------------------------------------------- 2 DBTaskCreateAccount::DBTaskCreateAccount(const Network::Address& addr, 3 std::string& registerName, 4 std::string& accountName, 5 std::string& password, 6 std::string& postdatas, 7 std::string& getdatas): 8 DBTask(addr), 9 registerName_(registerName), 10 accountName_(accountName), 11 password_(password), 12 postdatas_(postdatas), 13 getdatas_(getdatas), 14 success_(false) 15 { 16 } 17 18 //------------------------------------------------------------------------------------- 19 DBTaskCreateAccount::~DBTaskCreateAccount() 20 { 21 } 22 23 //------------------------------------------------------------------------------------- 24 bool DBTaskCreateAccount::db_thread_process() 25 { 26 ACCOUNT_INFOS info; 27 success_ = DBTaskCreateAccount::writeAccount(pdbi_, accountName_, password_, postdatas_, info) && info.dbid > 0; 28 return false; 29 } 30 31 //------------------------------------------------------------------------------------- 32 bool DBTaskCreateAccount::writeAccount(DBInterface* pdbi, const std::string& accountName, 33 const std::string& passwd, const std::string& datas, ACCOUNT_INFOS& info) 34 { 35 info.dbid = 0; 36 if(accountName.size() == 0) 37 { 38 return false; 39 } 40 41 // 尋找dblog是否有此賬號, 如果有則創建失敗 42 // 如果沒有則向account表新建一個entity數據同時在accountlog表寫入一個log關聯dbid 43 EntityTables& entityTables = EntityTables::findByInterfaceName(pdbi->name()); 44 KBEAccountTable* pTable = static_cast<KBEAccountTable*>(entityTables.findKBETable("kbe_accountinfos")); 45 KBE_ASSERT(pTable); 46 47 ScriptDefModule* pModule = EntityDef::findScriptModule(DBUtil::accountScriptName()); 48 if(pModule == NULL) 49 { 50 ERROR_MSG(fmt::format("DBTaskCreateAccount::writeAccount(): not found account script[{}], create[{}] error!\n", 51 DBUtil::accountScriptName(), accountName)); 52 53 return false; 54 } 55 56 if(pTable->queryAccount(pdbi, accountName, info) && (info.flags & ACCOUNT_FLAG_NOT_ACTIVATED) <= 0) 57 { 58 if(pdbi->getlasterror() > 0) 59 { 60 WARNING_MSG(fmt::format("DBTaskCreateAccount::writeAccount(): queryAccount error: {}\n", 61 pdbi->getstrerror())); 62 } 63 64 return false; 65 } 66 67 bool hasset = (info.dbid != 0); 68 if(!hasset) 69 { 70 info.flags = g_kbeSrvConfig.getDBMgr().accountDefaultFlags; 71 info.deadline = g_kbeSrvConfig.getDBMgr().accountDefaultDeadline; 72 } 73 74 DBID entityDBID = info.dbid; 75 76 if(entityDBID == 0) 77 { 78 // 防止多線程問題, 這里做一個拷貝。 79 MemoryStream copyAccountDefMemoryStream(pTable->accountDefMemoryStream()); 80 81 entityDBID = EntityTables::findByInterfaceName(pdbi->name()).writeEntity(pdbi, 0, -1, 82 ©AccountDefMemoryStream, pModule); 83 } 84 85 KBE_ASSERT(entityDBID > 0); 86 87 info.name = accountName; 88 info.email = accountName + "@0.0"; 89 info.password = passwd; 90 info.dbid = entityDBID; 91 info.datas = datas; 92 93 if(!hasset) 94 { 95 if(!pTable->logAccount(pdbi, info)) 96 { 97 if(pdbi->getlasterror() > 0) 98 { 99 WARNING_MSG(fmt::format("DBTaskCreateAccount::writeAccount(): logAccount error:{}\n", 100 pdbi->getstrerror())); 101 } 102 103 return false; 104 } 105 } 106 else 107 { 108 if(!pTable->setFlagsDeadline(pdbi, accountName, info.flags & ~ACCOUNT_FLAG_NOT_ACTIVATED, info.deadline)) 109 { 110 if(pdbi->getlasterror() > 0) 111 { 112 WARNING_MSG(fmt::format("DBTaskCreateAccount::writeAccount(): logAccount error:{}\n", 113 pdbi->getstrerror())); 114 } 115 116 return false; 117 } 118 } 119 120 return true; 121 } 122 123 //------------------------------------------------------------------------------------- 124 thread::TPTask::TPTaskState DBTaskCreateAccount::presentMainThread() 125 { 126 DEBUG_MSG(fmt::format("Dbmgr::reqCreateAccount: {}.\n", registerName_.c_str())); 127 128 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 129 (*pBundle).newMessage(LoginappInterface::onReqCreateAccountResult); 130 SERVER_ERROR_CODE failedcode = SERVER_SUCCESS; 131 132 if(!success_) 133 failedcode = SERVER_ERR_ACCOUNT_CREATE_FAILED; 134 135 (*pBundle) << failedcode << registerName_ << password_; 136 (*pBundle).appendBlob(getdatas_); 137 138 if(!this->send(pBundle)) 139 { 140 ERROR_MSG(fmt::format("DBTaskCreateAccount::presentMainThread: channel({}) not found.\n", addr_.c_str())); 141 Network::Bundle::reclaimPoolObject(pBundle); 142 } 143 144 return thread::TPTask::TPTASK_STATE_COMPLETED; 145 }
這個類里面,最重要的函數是 virtual bool db_thread_process();,DBTaskCreateAccount繼承自DBTask,DBTask繼承自DBTaskBase,在DBTaskBase中有一個process函數,會調用db_thread_process(),然后記錄執行所花的時間,而DBTaskBase繼承自TPTask,TPTask又是Task的子類,眾多Task組成了一個隊列Tasks,Tasks是一個任務隊列,不停地調用子類的process方法,接收外部任務的入隊請求,並且自動的處理任務。因為嵌套太深,這里就不詳細列了,但是代碼寫的真的很優秀,推薦代碼控們去看一下。如果C++基礎不深看不懂也沒關系,反正記住結論,db_thread_process,是子類真正做事情的地方。
並且,這個類里,presentMainThread這個函數,是持久化執行完的回調調用,在這里持久化結束以后調用的函數就是onReqCreateAccountResult
回歸正題,db_thread_process執行到logAccount,這個函數進行了具體的寫入。根據數據庫類型的不一樣,這里具體調用的方法也不一樣,我們看mysql的
1 //------------------------------------------------------------------------------------- 2 bool KBEAccountTableMysql::logAccount(DBInterface * pdbi, ACCOUNT_INFOS& info) 3 { 4 std::string sqlstr = "insert into kbe_accountinfos (accountName, password, bindata, email, entityDBID, flags, deadline, regtime, lasttime) values("; 5 6 char* tbuf = new char[MAX_BUF > info.datas.size() ? MAX_BUF * 3 : info.datas.size() * 3]; 7 8 mysql_real_escape_string(static_cast<DBInterfaceMysql*>(pdbi)->mysql(), 9 tbuf, info.name.c_str(), info.name.size()); 10 11 sqlstr += "\""; 12 sqlstr += tbuf; 13 sqlstr += "\","; 14 15 mysql_real_escape_string(static_cast<DBInterfaceMysql*>(pdbi)->mysql(), 16 tbuf, info.password.c_str(), info.password.size()); 17 18 sqlstr += "md5(\""; 19 sqlstr += tbuf; 20 sqlstr += "\"),"; 21 22 mysql_real_escape_string(static_cast<DBInterfaceMysql*>(pdbi)->mysql(), 23 tbuf, info.datas.data(), info.datas.size()); 24 25 sqlstr += "\""; 26 sqlstr += tbuf; 27 sqlstr += "\","; 28 29 mysql_real_escape_string(static_cast<DBInterfaceMysql*>(pdbi)->mysql(), 30 tbuf, info.email.c_str(), info.email.size()); 31 32 sqlstr += "\""; 33 sqlstr += tbuf; 34 sqlstr += "\","; 35 36 kbe_snprintf(tbuf, MAX_BUF, "%" PRDBID, info.dbid); 37 sqlstr += tbuf; 38 sqlstr += ","; 39 40 kbe_snprintf(tbuf, MAX_BUF, "%u", info.flags); 41 sqlstr += tbuf; 42 sqlstr += ","; 43 44 kbe_snprintf(tbuf, MAX_BUF, "%" PRIu64, info.deadline); 45 sqlstr += tbuf; 46 sqlstr += ","; 47 48 kbe_snprintf(tbuf, MAX_BUF, "%" PRTime, time(NULL)); 49 sqlstr += tbuf; 50 sqlstr += ","; 51 52 kbe_snprintf(tbuf, MAX_BUF, "%" PRTime, time(NULL)); 53 sqlstr += tbuf; 54 sqlstr += ")"; 55 56 SAFE_RELEASE_ARRAY(tbuf); 57 58 // 如果查詢失敗則返回存在, 避免可能產生的錯誤 59 if(!pdbi->query(sqlstr.c_str(), sqlstr.size(), false)) 60 { 61 ERROR_MSG(fmt::format("KBEAccountTableMysql::logAccount({}): sql({}) is failed({})!\n", 62 info.name, sqlstr, pdbi->getstrerror())); 63 64 return false; 65 } 66 67 return true; 68 }
至此,注冊流程持久化部分完成,回頭繼續我們的注冊流程,查看持久化處理完之后的回調onReqCreateAccountResult
1 //------------------------------------------------------------------------------------- 2 void Loginapp::onReqCreateAccountResult(Network::Channel* pChannel, MemoryStream& s) 3 { 4 SERVER_ERROR_CODE failedcode; 5 std::string accountName; 6 std::string password; 7 std::string retdatas = ""; 8 9 s >> failedcode >> accountName >> password; 10 s.readBlob(retdatas); 11 12 // 把請求交由腳本處理 13 SCOPED_PROFILE(SCRIPTCALL_PROFILE); 14 PyObject* pyResult = PyObject_CallMethod(getEntryScript().get(), 15 const_cast<char*>("onCreateAccountCallbackFromDB"), 16 const_cast<char*>("sHy#"), 17 accountName.c_str(), 18 failedcode, 19 retdatas.c_str(), retdatas.length()); 20 21 if(pyResult != NULL) 22 { 23 Py_DECREF(pyResult); 24 } 25 else 26 { 27 SCRIPT_ERROR_CHECK(); 28 } 29 30 DEBUG_MSG(fmt::format("Loginapp::onReqCreateAccountResult: accountName={}, failedcode={}.\n", 31 accountName.c_str(), failedcode)); 32 33 PendingLoginMgr::PLInfos* ptinfos = pendingCreateMgr_.remove(accountName); 34 if(ptinfos == NULL) 35 return; 36 37 Network::Channel* pClientChannel = this->networkInterface().findChannel(ptinfos->addr); 38 if(pClientChannel == NULL) 39 return; 40 41 pClientChannel->extra(""); 42 43 Network::Bundle* pBundle = Network::Bundle::createPoolObject(); 44 (*pBundle).newMessage(ClientInterface::onCreateAccountResult); 45 (*pBundle) << failedcode; 46 (*pBundle).appendBlob(retdatas); 47 48 pClientChannel->send(pBundle); 49 SAFE_RELEASE(ptinfos); 50 }
,服務器端給客戶端發消息了,我們來看客戶端怎么處理的
1 /* 2 賬號創建返回結果 3 */ 4 public void Client_onCreateAccountResult(MemoryStream stream) 5 { 6 UInt16 retcode = stream.readUint16(); 7 byte[] datas = stream.readBlob(); 8 9 Event.fireOut("onCreateAccountResult", new object[]{retcode, datas}); 10 11 if(retcode != 0) 12 { 13 Dbg.WARNING_MSG("KBEngine::Client_onCreateAccountResult: " + username + " create is failed! code=" + retcode + "!"); 14 return; 15 } 16 17 Dbg.DEBUG_MSG("KBEngine::Client_onCreateAccountResult: " + username + " create is successfully!"); 18 }
至此,注冊流程完畢。
事實上,KBE大部分的系統消息流程不會這么麻煩,在python層很簡單的幾行代碼就完成一個系統。只不過因為KBE注冊登錄是C++內嵌代碼的原因,所以才會格外復雜。對於對引擎內部機制不關心的人來說,這篇文章完全可以不看。也不會影響工作的效率和速度,必定基本上所有的代碼都是python來寫的。
我之所以寫這篇文章,也是希望通過寫這篇文章,讓自己對KBE引擎底層的邏輯處理有一個系統的了解和記錄。作為一個服務器主程,不能底層一點也改不了,這是我的初衷。
登錄流程比注冊流程要簡單很多,可以仿照本文的閱讀流程讀一遍。
留一點練習題吧,假如我們需要在賬號信息中中新加一個字段,設備唯一標識碼,應該怎么做?
我是青島遠碩信息科技發展有限公司的Peter,如果轉載的話,請保留這段文字。
