「登录」前需要加载通信协议,加载过程需要在服务器与客户端之间进行函数交互调用。
理清问题
这篇文章里,我们将要理清前文《阅读 KBEngine 要理清的问题》提到的问题
- 服务器端与客户端是怎么交互的?
先给出结论,具体可看文章最后的「小结」
通过在服务器端和客户端分别解析 res/server/messages_fixed.xml 中声明的协议来完成交互。 其实就是分别构建 <messageId,回调函数> 键值对。那么,客户端是怎么知道如何解析这个协议的呢?
我们接着上文《KBEngine Cocos2d JS 客户端启动过程》来一步步说明,上文最后提到,流程进入到 plugins/kbengine_js_plugins/kbengine.js 里的 login 函数。
kbengine.js / login
login 函数中,3086行参数为 true 来调用 login_loginapp,所以逻辑会进入 3093~3096 行,即使用 ws 协议访问在 main.js 里配置的 ip:port;在 connect 成功后的回调函数是 kbengins.js 中的 onOpenLoginapp_login。
ws 协议介绍,这里给出部分截图,便于理解。
kbengine.js / onOpenLoginapp_login
opOpenLoginapp_login 主要有几个逻辑:
- 2600 行,设置 currentserver = 'loginapp'
- 2601 行,设置 currentstate = 'login'
- 2605~2610 行,访问服务器端的 loginapp::importClientMessages 函数,回调函数是 kbengins.js 中的 Client_onImportClientMessages 函数
为什么 2605~2607 行能访问到 loginapp 中的 importClientMessages 函数?
因为 2066 行, KBEngine.messages.Loginapp_importClientMessages 对应的 messageId 在 服务器端 loginapp 中的处理函数是 importClientMessage 函数。这个配对需要在客户端和服务器端都进行声明。
客户端在 kbengine.js 中声明了 Loginapp_importClientMessages 对应的 messageId = 5. 如下图所示:
服务器端在 res/server/messages_fixed.xml 中声明了 messageId = 5 对应的处理函数为 Loginapp 中 importClientMessage 函数。如下图所示:
loginapp.cpp / importClientMessages
服务器端的 loginapp.cpp 中的 importClientMessages 函数,向客户端发送协议信息。
有几个主要逻辑
- 5~24 行,取所有 Client 中标为 Exposed 的协议
- 26~47 行,取所有 Loginapp 中标为 Exposed 的协议
- 49 行,设置发送回客户端的 messageId,这个与客户端的 messageId 要一致,下文有进一步说明。
- 54~78 行,数据塞到流里
- 81 行,发送到客户端
- 17 行,回调函数名称,如 Loginapp::login 改为 Loginapp_login;这个很重要,这里定义了消息名称,客户端会拿这个来回调 loginapp 的函数
kbengine.js / Client_onImportClientMessages
由于前文设置了 onmessage = Client_onImportClientMessages,根据 ws 协议,服务器返回信息时,就会调用到这个函数。
Client_onImportClientMessages 有几个主要逻辑:
- 2972 行,先判断读取到的 messageId 是否为 KBEngine.messages.onImportClientMessages,可见服务器也是以 messageId 来标识客户端的处理函数的。这里确实在服务器端和客户端都做了配对。
服务器端在 res/server/messages_fixed.xml 中声明了 messageId = 518 对应的处理函数为 客户端 中 importClientMessage 函数。如下图所示:
客户端在 kbengine.js 中声明了 onImportClientMessages 对应的 messageId = 518. 如下图所示:
服务器端是如何访问客户端的,将在讲解服务器端代码时说明。
- 2983~3022行,代码先读取 msgid(2983行),然后看 msgname 是否带有 Client_(2996行)
如果有,就是 Client 回调函数(即 kbengine.js 中的以 Client_ 开头的函数,loginapp 服务器往客户端发送协议时,就会把 messages_fixed.xml 中的声明 Client::xxx 改成 Client_xxx 作为函数名),就把 msgid -> Client_xxx 添加到 KBEngine.clientmessage 中;
否则,就是服务器的函数,因为服务器分为 baseapp、loginapp 等,就要分别按 currserver 来存放,前面 (2600 行)设置了 currentserver ='loginapp',所以这里取到的,除去 client 之外,就是 loginapp 中的回调函数;
至此,在客户端中加载 client 和 loginapp 中的协议完成。
- 3026 行, Client_onImportClientMessages 的最后,会调用 onImportClientMessageCompleted 函数。
kbengine.js / onImportClientMessageCompleted
onImportClientMessageCompleted 有几个逻辑:
- 2643行,设置 socket.onmessage 为 app.onmessage
- 2644 行,hello 函数 (握手,这个不属于协议加载,将在后面细讲)
- 还记得前面 2600、2601 行设置的吧,此时 currentserver == 'loginapp',currstate == 'login',所以会执行到 2658 行,即此时才会真正开始发送用户名、密码进行登录。
kbengine.js / onmessage
由前面可知,协议加载完成后,在 onImportClientMessageCompleted 里的 2643 行,会把 socket 的 onmessage 置为上图的 onmessage 函数。
逻辑很简单,就是取到 msgid,然后在 KBEngine.clientmessage 中取到相应的 handler,然后执行即可。由此,服务器端就可以调用到客户端的函数。
小结
抛开前面各种回调,我们来说明一下 KBEngine 的协议机制。为简化起见,这里只说明 loginapp 与 client 进行交互的协议。
- 在 res/server/messages_fixed.xml 中,以 <Client::xxx></Client::xxx> 的形式声明客户端将要提供的函数,以<Loginapp:xxx></Loginapp::xxx> 的形式声明 loginapp 将要提供的函数
- 客户端(这里是 kbengins.js)实现具体的函数
- loginapp 实现具体的函数
- loginapp 加载 message_fixed.xml 并解析,首先建立 <messageId, 3 中实现的函数>的映射关系,即此时客户端可以通过一个 messageId 调用到 loginapp 的函数;其次知道客户端有哪些函数可以被调用,这些函数的 messageId 分别是什么。
- 客户端没法直接加载 message_fixed.xml,只能通过 loginapp 来加载协议;
- 客户端在加载协议之前,不知道 loginapp 的返回协议的函数对应的 id;所以协议加载函数的 id 必须在客户端写死,也就是前面提到的 messageId = 5,这个 messageId 传到 loginapp 服务器之后,就能调到 Loginapp::importClientMessage 函数;并返回协议信息。
- 客户端拿到协议信息,首先建立 <messageId,2 中实现的函数>的映射关系,即此时 loginapp 服务器可以通过一个 messageId 调用到客户端的函数;其次,知道 loginapp 服务器端有哪些函数可以被调用,这些函数的 messageId 分别是什么。
- 至此,客户端和 loginapp 服务器端都分别建立起 <message,回调函数> 的映射关系,即协议构建成功。
- 最后补充一下,服务器与客户端交互都是通过 MemoryStream 来传递数据,先传递 messageId,然后传递参数;
客户端/loginapp 服务器端读取网络数据时,先读取到 messageId,就知道要调用哪个函数,然后就把后面的参数取出来,进行具体调用即可。
客户端的这个过程,可以参看前面的 onmessage 函数。
服务器的这个过程,请看后文《KBEngine 服务器端-loginapp-协议构建、解析执行》。