天天看点

pomelo源码解析之模块解析(四)pomelo-rpc

文章目录

  • 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本身是一个动态类型语言。最终调用方式跟函数调用看起来并没有什么区别。

而且这种动态语言的异步调用用起来也是比较舒服。