文章目录
- pomelo-rpc
-
- pomelo-rpc/lib/rpc-server 服务器:
- pomelo-rpc/lib/rpc-client 客户端
- 总结
-
pomelo-rpc
RPC服务是用于进程间通讯的一项技术,底层通讯基于TCP或者更上层的mqtt,ws等通信协议
对用户屏蔽底层细节,进程间通讯形如函数调用,用起来比较方便
传统开发一个进程间通讯业务,例如logonServer和gameServer之间通讯,稍微封装下,接口大概是这样子的:
// logonServer.h
sendMsgToGameServer(msgID, msg);
这样也是屏蔽了底层通信细节,但调用了服务器哪个函数处理的却无法知道。
而RPC方式可以让你像调用函数一样进行进程间的通讯。一个好的RPC底层还会封装好几种协议格式。
整体来看还是比较方便的。
下边就来具体分析下pomelo-rpc的具体实现逻辑
-
pomelo-rpc/lib/rpc-server 服务器:
RPC服务器就是监听一个端口,根据收到的RPC请求调用不同的模块处理并反馈
先看一下服务器支持的通信协议 rpc-server/acceptors
可以发现支持mqtt mqtt2 tcp ws ws2总共5种通信协议,处理逻辑没有区别,只是编码格式等有区别
负责监听端口,收到消息后在processMsg中处理,会调用自身的acceptor.cb去处理
对外接口只有一个create // pomelo-rpc/lib/server.js
入参:
{
paths: [
{
namespace: ?, // 自定义命名
path: ?, // 需要RPC的模块路径
},
],
context: ?, // 载入模块传入的构造参数
port: ?, // 监听端口
acceptorFactory: ?, // 采用的消息协议工厂方法 默认mqtt
reloadRemotes: ?, // 是否动态重载 会启用文件监听 改变模块后自动重载
};
分析server.loadRemoteServices可以发现
server会根据传入的路径和命名,扫描该路径下的所有js文件。
最终生成形如:
res = {
namespace: {
filename: module,
},
}
然后传入gateway中,gateway是server的实际逻辑对象
gateway的构造函数中可以发现会创建一个接收器,并写入一个回调函数执行dispatch.route
在dispatch.route可以看到,最终调用了保存模块中的具体函数进行处理
再具体模块中处理后,可以调用回调,最终回到具体的接收器中processMsg,在这里给客户端做回馈
-
pomelo-rpc/lib/rpc-client 客户端
客户端作为RPC的调用方主要是发送以及接收反馈
跟服务器对应,支持5种通信协议
mailboxes就是每种协议的具体实现,支持连接超时,心跳,心跳超时,反馈超时的处理:断开连接
在发送消息时MailBox.prototype.send会把回调函数存起来,在收到消息后找到对应的回调调用processMsg
对外接口create入参:
{
context: ?, // 载入模块传入的构造参数
routeContext: ?, // 自定义路由参数
router: ?, // 自定义路由跳转
routerType: ?, // 设置路由类型
rpcDebugLog: ?, // 是否需要日志
mailboxFactory: ?, // 采用的消息协议工厂方法 默认mqtt
pendingSize: ?, // 发送缓冲大小
};
对于客户端来说,也需要知道能RPC到哪里去
添加服务器信息:服务器的连接信息存在client.addServer
添加对应的RPC信息:client.addProxies
在这里发现也需要跟服务器读取相同的文件夹,或者新建一个文件夹保持与服务器同步(这里只需要函数名即可)
在client.generateProxy中可以看到:
res[name] = Proxy.create一路跟进去到proxy.genFunctionProxy可以看到,实际上并没有载入具体的函数内容
而是根据函数名生成了一个具有两个函数的对象proxy和toServer,而回调是绑定在了client.proxCB上
最终client形如:
client = {
...
proxies: {
namespace: {
servertype: {
methodname: method, // 这个methond不是模块中的函数而是proxy生成的包含2个函数的对象
};
};
};
};
调用就是
client.proxies.namespace.servertype.methodname.proxy(toServer);
最终还是回到client.proxyCB。之所以绕了这么一圈就是为了让通信调用过程像函数调用一样
toServer是指明某个server进行通信,proxy是按参数找一个通信
整体来说,采用这一套RPC做进程间全双工通讯的话,需要每一个进程都启动 RPC服务器 + RPC客户端
-
总结
经过源码分析我们发现RPC的整体实现逻辑还是比较简单的。这主要得益于js本身是一个动态类型语言。最终调用方式跟函数调用看起来并没有什么区别。
而且这种动态语言的异步调用用起来也是比较舒服。