码迷,mamicode.com
首页 > 其他好文 > 详细

Start KBE 01. 注册流程解读

时间:2016-04-27 22:38:29      阅读:446      评论:0      收藏:0      [点我收藏+]

标签:

刚刚接触KBE,之前还是信心满满地以为很容易就能学会,但是当一切细节展现在眼前的时候,才发现这样一个庞然大物放在面前,竟然不知道该从何处下手。

与其左顾右盼,犹豫不决还不如看看源码,所谓庖丁解牛,恢恢乎游刃有余。

(本文参考的例子是kbengine-0.8.2 + kbengine_cocos2d_js_demo-0.8.0。)

废话少说,就从注册流程开始看起吧,这是最基础也是最简单的一个功能了吧。 Step by step,我只分析了简要的流程,并没有做更深入的挖掘。

 

首先我们看看他的主体流程:

1. 客户端发起请求

2. loginApp处理消息

3. dbmgr 处理消息,并将数据写入db,回包

4. loginApp收到回包并转发

5. 客户端收到回包

怎么样,是不是简单的不得了,只牵涉到loginApp和dbmgr两个进程。

 

下面我们通过源代码,仔细看看到底发生了些什么事情。

1. 客户端发起请求:

客户端首先相应用户点击“注册”

cocos2d-js-client\src\cc_scripts\StartScene.js

 1 touchRegisterButtonEvent: function (sender, type) 
 2     {
 3         switch (type) {
 4             case ccui.Widget.TOUCH_BEGAN:
 5                 break;
 6             case ccui.Widget.TOUCH_MOVED:
 7                 break;
 8             case ccui.Widget.TOUCH_ENDED:
 9                 GUIDebugLayer.debug.INFO_MSG("Connect to server...");
10                 KBEngine.Event.fire("createAccount", this.usernamebox.getString(), this.passwordbox.getString(), "kbengine_cocos2d_js_demo");            
11                 break;
12             case ccui.Widget.TOUCH_CANCELED:
13                 break;
14             default:
15                 break;
16         }
17     },

 那么KBEngine.Event.fire到底是个什么鬼呢?其实这是实现在plugin/kbengine_js_plugins/kbengine.js 的本地事件的派发机制,看看它的另外一个接口: KBEngine.Event.register 应该就清楚了。

this.register = function(evtName, classinst, strCallback)

不用在意细节,反正register用来注册evntName的回调函数,fire用来派发evntName事件,从而解除发送端与响应端的紧耦合。

仔细观察代码,就不难发现,其实"createAccout"的回调早就已经注册好了

plugin/kbengine_js_plugins/kbengine.js

KBEngine.Event.register("createAccount", this, "createAccount");

再看看createAccount的实现吧,

plugin/kbengine_js_plugins/kbengine.js

this.createAccount = function(username, password, datas)
	{  
		KBEngine.app.username = username;
		KBEngine.app.password = password;
		KBEngine.app.clientdatas = datas;
		
		KBEngine.app.createAccount_loginapp(true);
	}
	
	this.createAccount_loginapp = function(noconnect)
	{  
		if(noconnect)
		{
			KBEngine.INFO_MSG("KBEngineApp::createAccount_loginapp: start connect to ws://" + KBEngine.app.ip + ":" + KBEngine.app.port + "!");
			KBEngine.app.connect("ws://" + KBEngine.app.ip + ":" + KBEngine.app.port);
			KBEngine.app.socket.onopen = KBEngine.app.onOpenLoginapp_createAccount;  
		}
		else
		{
			var bundle = new KBEngine.Bundle();
			bundle.newMessage(KBEngine.messages.Loginapp_reqCreateAccount);
			bundle.writeString(KBEngine.app.username);
			bundle.writeString(KBEngine.app.password);
			bundle.writeBlob(KBEngine.app.clientdatas);
			bundle.send(KBEngine.app);
		}
	}

OK,这是把消息发送到loginApp了啊!

值得一提的是,代码在这里绕了一个弯:bundle是的作用是收集并整理数据,KBEngine.app里面封装了一个webSocket,与其说是bundle.send(KBEngine.app),还不说是KBEngine.app.send(bundle.data)更好理解。

更深的东西这里就不提了,因为我也没有去研究。。。

 

2.loginApp处理消息

直接看收包的代码:

void Loginapp::reqCreateAccount(Network::Channel* pChannel, MemoryStream& s)
{
	std::string accountName, password, datas;

	s >> accountName >> password;
	s.readBlob(datas);
	
	if(!_createAccount(pChannel, accountName, password, datas, ACCOUNT_TYPE(g_serverConfig.getLoginApp().account_type)))
		return;
}

提取数据,然后将参数转发给_createAccount。_createAccount的代码有250多行,这里只截取一些重要的,并加以解释:

\kbe\src\server\loginapp\loginapp.cpp

bool Loginapp::_createAccount(Network::Channel* pChannel, std::string& accountName, 
								 std::string& password, std::string& datas, ACCOUNT_TYPE type)
{
	AUTO_SCOPED_PROFILE("createAccount");

     /*
      首先是一系列的检查,检查数据库是否开放注册,检查用户名,密码长度是否越界。这些代码并不是最重要的,已经被我移除。
*/  
     /*
      下面这段代码做了什么事情呢? PengdingLoginMgr本质上管理着一个从AccountName到ClientInfo的映射关系。
      当客户端发出注册请求的时候,loginApp将会检查AccountName是否已经存在于PendingLoginMgr当中,若已经存在,那么就发送err的回包,
      如果不存在,那么允许继续处理。在后面的篇幅中,你会发现一旦用户注册成功,那么AccountName就会从PendingLoginMgr中删除。
      这样做的好处在于不同用户同时请求相同的账号,能够快速地给与回复,而不需要再从数据走一圈,同时又巧妙地保存了请求端client的相关信息
     */
	PendingLoginMgr::PLInfos* ptinfos = pendingCreateMgr_.find(const_cast<std::string&>(accountName));
	if(ptinfos != NULL)
	{
		WARNING_MSG(fmt::format("Loginapp::_createAccount: pendingCreateMgr has {}, request create failed!\n", 
			accountName));

		Network::Bundle* pBundle = Network::Bundle::ObjPool().createObject();
		(*pBundle).newMessage(ClientInterface::onCreateAccountResult);
		SERVER_ERROR_CODE retcode = SERVER_ERR_BUSY;
		(*pBundle) << retcode;
		(*pBundle).appendBlob(retdatas);
		pChannel->send(pBundle);
		return false;
	}
	
    /*
      下面的代码其实是KBE的亮点之一,将一部分控制逻辑交由py脚本处理。我只保留了核心的代码。
*/ { // 把请求交由脚本处理 SERVER_ERROR_CODE retcode = SERVER_SUCCESS; SCOPED_PROFILE(SCRIPTCALL_PROFILE); PyObject* pyResult = PyObject_CallMethod(getEntryScript().get(), const_cast<char*>("onRequestCreateAccount"), const_cast<char*>("ssy#"), accountName.c_str(), password.c_str(), datas.c_str(), datas.length()); /*这里对py脚本的结果做处理,若py处理结果为err,那么loginApp将回复给client对应的消息*/ }
    /*
      接下来又是很长一大推代码,又是检查用户名的合法性。
      可以注册的用户名类型分为ACCOUNT_TYPE_SMART, ACCOUNT_TYPE_NORMAL, ACCOUNT_TYPE_MAIL。
      若客户端带上来的类型是Normal的,就做一些最简单的判断。实现在validName函数内部,用一串模式匹配字符串来验证。
      若客户端带上来的类型是Email的,就做邮箱格式的判断。实现在email_isvalid函数内部。
      若客户端带上来的类型是Smart的,那就先推导它的类型到底是Normal或者Email,然后在验证。
      代码太长,已经被删除。
    */
    /*
      好了,至此我们已经通过了绝大多数的验证。可以把AccountName加入到PendingLoginMgr里面去了。
    */ ptinfos = new PendingLoginMgr::PLInfos; ptinfos->accountName = accountName; ptinfos->password = password; ptinfos->datas = datas; ptinfos->addr = pChannel->addr(); pendingCreateMgr_.add(ptinfos); Components::COMPONENTS& cts = Components::getSingleton().getComponents(DBMGR_TYPE); Components::ComponentInfos* dbmgrinfos = NULL;     /*
      一系列验证,略
    */ pChannel->extra(accountName); /*最后,向dbmgrApp发送注册请求*/ Network::Bundle* pBundle = Network::Bundle::ObjPool().createObject(); (*pBundle).newMessage(DbmgrInterface::reqCreateAccount); uint8 uatype = uint8(type); (*pBundle) << accountName << password << uatype; (*pBundle).appendBlob(datas); dbmgrinfos->pChannel->send(pBundle); return true; }

  

  这里插入一段py脚本的用户名密码判断函数,

 

def onRequestCreateAccount(accountName, password, datas):
	"""
	KBEngine method.
	请求账号创建时回调
	"""
	INFO_MSG(‘onRequestCreateAccount() %s‘ % (accountName))

	errorno = KBEngine.SERVER_SUCCESS
	
	if len(accountName) > 64:
		errorno = KBEngine.SERVER_ERR_NAME;

	if len(password) > 64:
		errorno = KBEngine.SERVER_ERR_PASSWORD;
		
	return (errorno, accountName, password, datas)

  引擎会找到kbengine_demos_assets\scripts\login目录下,名为kbengine.py的文件,调用里面的"onRequestCreateAccount"方法。这个函数名写死在c++里,不能随意修改。

 

3. dbmgr 处理消息,并将数据写入db,回包

 

直接看收包代码:

\src\server\dbmgr\dbmgr.cpp

void Dbmgr::reqCreateAccount(Network::Channel* pChannel, KBEngine::MemoryStream& s)
{
	std::string registerName, password, datas;
	uint8 uatype = 0;

	s >> registerName >> password >> uatype;
	s.readBlob(datas);

	if(registerName.size() == 0)
	{
		ERROR_MSG("Dbmgr::reqCreateAccount: registerName is empty.\n");
		return;
	}

	pInterfacesAccountHandler_->createAccount(pChannel, registerName, password, datas, ACCOUNT_TYPE(uatype));
	numCreatedAccount_++;
}

  OK,很简单,没啥好说的,然后看createAccout函数

\src\server\dbmgr\interfaces_handler.cpp

//-------------------------------------------------------------------------------------
bool InterfacesHandler_Dbmgr::createAccount(Network::Channel* pChannel, std::string& registerName,
										  std::string& password, std::string& datas, ACCOUNT_TYPE uatype)
{
	std::string dbInterfaceName = Dbmgr::getSingleton().selectAccountDBInterfaceName(registerName);

	thread::ThreadPool* pThreadPool =DBUtil::pThreadPool(dbInterfaceName);
	if (!pThreadPool)
	{
		ERROR_MSG(fmt::format("InterfacesHandler_Dbmgr::createAccount: not found dbInterface({})!\n",
			dbInterfaceName));

		return false;
	}

	// 如果是email,先查询账号是否存在然后将其登记入库
	if(uatype == ACCOUNT_TYPE_MAIL)
	{
		pThreadPool->addTask(new DBTaskCreateMailAccount(pChannel->addr(),
			registerName, registerName, password, datas, datas));

		return true;
	}

	pThreadPool->addTask(new DBTaskCreateAccount(pChannel->addr(),
		registerName, registerName, password, datas, datas));

	return true;
}

  也很简单,创建了一个任务,加入到线程池里面去。

  selectAccountDBInterfaceName其实是获取数据库的配置信息,它记载在一个xml文件里,默认地只有一个default项目:

      kbe\res\server\kbengine_defs.xml

<databaseInterfaces>
			<!-- 数据库接口名称 (可以定义多个不同的接口,但至少存在一个default)
				(Database interface name)
			-->
			<default>
				<!-- 如果为true,则为纯净的数据库,引擎不创建实体表 
					(If true is pure database, engine does not create the entity table)
				-->
				<pure> false </pure>

				<!-- 数据库类型 (mysql、redis)
					(Database type(mysql, redis))
				-->
				<type> mysql </type>											<!-- Type: String -->

				<!-- 数据库地址 
					(Database address)
				-->
				<host> localhost </host>										<!-- Type: String -->
				<port> 3306 </port>												<!-- Type: Integer -->

				<!-- 数据库账号验证 
					(Database auth)
				-->
				<auth>  
					<username> kbeDataBase </username>									<!-- Type: String -->
					<password> 123456 </password>									<!-- Type: String -->
					
					<!-- 为true则表示password是加密(rsa)的, 可防止明文配置 
						(is true, password is RSA)
					-->
					<encrypt> true </encrypt>
				</auth>

				<!-- 数据库名称 
					(Database name)
				-->
				<databaseName> kbeGameDataBase </databaseName> 								<!-- Type: String -->
				
				<!-- 数据库允许的连接数 
					(Number of connections allowed by the database)
				-->
				<numConnections> 5 </numConnections>							<!-- Type: Integer -->
				
				<!-- 字符编码类型 
					(Character encoding type)
				-->
				<unicodeString>
					<characterSet> utf8 </characterSet> 						<!-- Type: String -->
					<collation> utf8_bin </collation> 							<!-- Type: String -->
				</unicodeString>
			</default>
		</databaseInterfaces>

  下面,我们再来看看DBTaskCreateAccount实现了写什么东西:

bool DBTaskCreateAccount::db_thread_process()
{
	ACCOUNT_INFOS info;
	success_ = DBTaskCreateAccount::writeAccount(pdbi_, accountName_, password_, postdatas_, info) && info.dbid > 0;
	return false;
}

//-------------------------------------------------------------------------------------
bool DBTaskCreateAccount::writeAccount(DBInterface* pdbi, const std::string& accountName, 
									   const std::string& passwd, const std::string& datas, ACCOUNT_INFOS& info)
{
	info.dbid = 0;
	if(accountName.size() == 0)
	{
		return false;
	}

	// 寻找dblog是否有此账号, 如果有则创建失败
	// 如果没有则向account表新建一个entity数据同时在accountlog表写入一个log关联dbid
	EntityTables& entityTables = EntityTables::findByInterfaceName(pdbi->name());
	KBEAccountTable* pTable = static_cast<KBEAccountTable*>(entityTables.findKBETable("kbe_accountinfos"));
	KBE_ASSERT(pTable);

	ScriptDefModule* pModule = EntityDef::findScriptModule(DBUtil::accountScriptName());
	if(pModule == NULL)
	{
		ERROR_MSG(fmt::format("DBTaskCreateAccount::writeAccount(): not found account script[{}], create[{}] error!\n", 
			DBUtil::accountScriptName(), accountName));

		return false;
	}

	if(pTable->queryAccount(pdbi, accountName, info) && (info.flags & ACCOUNT_FLAG_NOT_ACTIVATED) <= 0)
	{
		if(pdbi->getlasterror() > 0)
		{
			WARNING_MSG(fmt::format("DBTaskCreateAccount::writeAccount(): queryAccount error: {}\n", 
				pdbi->getstrerror()));
		}

		return false;
	}

	bool hasset = (info.dbid != 0);
	if(!hasset)
	{
		info.flags = g_kbeSrvConfig.getDBMgr().accountDefaultFlags;
		info.deadline = g_kbeSrvConfig.getDBMgr().accountDefaultDeadline;
	}

	DBID entityDBID = info.dbid;
	
	if(entityDBID == 0)
	{
		// 防止多线程问题, 这里做一个拷贝。
		MemoryStream copyAccountDefMemoryStream(pTable->accountDefMemoryStream());

		entityDBID = EntityTables::findByInterfaceName(pdbi->name()).writeEntity(pdbi, 0, -1,
				&copyAccountDefMemoryStream, pModule);
	}

	KBE_ASSERT(entityDBID > 0);

	info.name = accountName;
	info.email = accountName + "@0.0";
	info.password = passwd;
	info.dbid = entityDBID;
	info.datas = datas;
	
	if(!hasset)
	{
		if(!pTable->logAccount(pdbi, info))
		{
			if(pdbi->getlasterror() > 0)
			{
				WARNING_MSG(fmt::format("DBTaskCreateAccount::writeAccount(): logAccount error:{}\n", 
					pdbi->getstrerror()));
			}

			return false;
		}
	}
	else
	{
		if(!pTable->setFlagsDeadline(pdbi, accountName, info.flags & ~ACCOUNT_FLAG_NOT_ACTIVATED, info.deadline))
		{
			if(pdbi->getlasterror() > 0)
			{
				WARNING_MSG(fmt::format("DBTaskCreateAccount::writeAccount(): logAccount error:{}\n", 
					pdbi->getstrerror()));
			}

			return false;
		}
	}

	return true;
}

(这段代码关于entityTable的作用我还没有看。)

DBTaskCreateAccount继承于Task接口,Task里定义了一个纯虚函数 virtual bool process() = 0; 当添加到线程池的任务被调度到的时候,process就将被访问。

DBTaskCreateAccount的process千转百转,最终就会调用到上面的writeAccount函数。很明显这里就是在写数据库了,pTable其实就是对数据库访问函数的一层封装,pTable->logAccount将会把账号写入。

注意这里保存着一个success的返回值,这个值在main线程中有用,他是判断数据库写入成功与否,如何回包的依据。

当一个任务被完成了,那么线程池就会把它放入到已经finish的task队列里。

这个ThreadPool的原理这里就不多说了,也许以后会花时间去研究下,但大致就是这样。

dbMgr的主线程会不停调度finish的task的方法,task::presentMainThread。下面是DBTaskCreateAccount::presentMainThread实现了

thread::TPTask::TPTaskState DBTaskCreateAccount::presentMainThread()
{
	DEBUG_MSG(fmt::format("Dbmgr::reqCreateAccount: {}.\n", registerName_.c_str()));

	Network::Bundle* pBundle = Network::Bundle::ObjPool().createObject();
	(*pBundle).newMessage(LoginappInterface::onReqCreateAccountResult);
	SERVER_ERROR_CODE failedcode = SERVER_SUCCESS;

	if(!success_)
		failedcode = SERVER_ERR_ACCOUNT_CREATE_FAILED;

	(*pBundle) << failedcode << registerName_ << password_;
	(*pBundle).appendBlob(getdatas_);

	if(!this->send(pBundle))
	{
		ERROR_MSG(fmt::format("DBTaskCreateAccount::presentMainThread: channel({}) not found.\n", addr_.c_str()));
		Network::Bundle::ObjPool().reclaimObject(pBundle);
	}

	return thread::TPTask::TPTASK_STATE_COMPLETED;
}

  看见没,之前的success变量起到作用了。dbMgr向LoginApp回包了。

 

4. loginApp收到回包并转发

代码很简单,无非就是把注册结果在py脚本里判断下,然后再回包给客户端。流程和发包有些类似,这里就不重复了。

 

5. 客户端收到回包

客户端收到回包,并把结果显示出来。

  plugins\kbengine_js_plugins\kbengine.js

this.Client_onCreateAccountResult = function(stream)
	{
		var retcode = stream.readUint16();
		var datas = stream.readBlob();
		
		if(retcode != 0)
		{
			KBEngine.ERROR_MSG("KBEngineApp::Client_onCreateAccountResult: " + KBEngine.app.username + " create is failed! code=" + KBEngine.app.serverErrs[retcode].name + "!");
			return;
		}

		KBEngine.Event.fire("onCreateAccountResult", retcode, datas);
		KBEngine.INFO_MSG("KBEngineApp::Client_onCreateAccountResult: " + KBEngine.app.username + " create is successfully!");
	}

  src\cc_scripts\StartScene.js  “onCreateAccountResult”的时间响应函数在下面,look,

onCreateAccountResult : function(retcode, datas)
    {
		if(retcode != 0)
		{
			GUIDebugLayer.debug.ERROR_MSG("CreateAccount is error(注册账号错误)! err=" + retcode);
			return;
		}
		
		//if(KBEngineApp.validEmail(stringAccount))
		//{
		//	GUIDebugLayer.debug.INFO_MSG("createAccount is successfully, Please activate your Email!(注册账号成功,请激活Email!)");
		//}
		//else
		{
			GUIDebugLayer.debug.INFO_MSG("CreateAccount is successfully!(注册账号成功!)");
		}    	
    },

  

这就是注册账号的整个流程了。

Start KBE 01. 注册流程解读

标签:

原文地址:http://www.cnblogs.com/alinne789/p/5437026.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!