# 远程调用

DCE利用PHP的自动加载特性,实现了较为简单优雅的RPC(远程过程调用),你可以很方便的做RPC开发。RPC依赖Swoole,只能在有Swoole的常驻内存环境使用。

# \dce\rpc\RpcServer

RPC服务类,注册暴露支持RPC的服务接口

# ::new()

创建一个服务实例

  • 参数

    • \dce\rpc\RpcHost|null $rpcHost = null RPC主机实例
  • 返回self

# ::host()

创建一个RPC主机实例

  • 参数

    • string $host 主机地址
    • int $port = RpcUtility::DEFAULT_TCP_PORT 绑定端口
  • 返回\dce\rpc\RpcHost

  • 示例

$host = \dce\rpc\RpcServer::host();
1

# ->addHost()

添加一个新主机

  • 参数

    • \dce\rpc\RpcHost $rpcHost RPC主机实例
  • 返回$this

  • 示例

$server = \dce\rpc\RpcServer::new(\dce\rpc\RpcServer::host());
$server->addHost(\dce\rpc\RpcServer::host('192.168.1.10', 20466));
1
2

# ->prepare()

预加载Rpc命名空间

  • 参数

    • string $wildcard 名字空间通配符
    • string $root 名字空间下服务类根目录
  • 返回$this

  • 示例

$server = \dce\rpc\RpcServer::new(\dce\rpc\RpcServer::host());
$server->prepare('rpc\\*', 'service/rpc/');
1
2

# ->preload()

预加载Rpc类文件

  • 参数

    • string $filename 需加载文件名
  • 返回$this

  • 示例

$server = \dce\rpc\RpcServer::new(\dce\rpc\RpcServer::host());
$server->preload('service/HelloRemote.php');
1
2

# ->start()

启动Rpc服务

  • 参数

    • bool $useAsyncServer = false 是否使用异步Tcp服务
      • true 使用异步版Tcp
      • false 使用协程版Tcp
  • 返回void

  • 示例

RpcServer::new(RpcServer::host())
  ->addHost(RpcServer::host('0.0.0.0', 20469)->setAuth('drunk'))
  ->addHost(RpcServer::host('0.0.0.0', 20468))
  ->prepare($this->request->project->path . "/service/rpc/", 'rpc\*')
  ->start();
1
2
3
4
5

# \dce\rpc\RpcHost

Rpc主机类,标记IP端口等信息

# ->setAuth()

设置鉴权方案

  • 参数

    • string $password 鉴权密码
    • array $ipWhiteList = [] 限制客户端IP白名单
    • bool $needNative = false 是否仅允许同服务器环境客户端
    • bool $needLocal = false 是否仅允许同局域网环境客户端
  • 返回$this

  • 示例

$host = RpcServer::host('0.0.0.0', 20469)->setAuth('drunk', [], false, true);
1

# \dce\rpc\RpcMatrix;

Rpc服务基类,Rpc服务接口必须继承该类才能提供远程调用。

Rpc服务接口实现类 必须不能 通过DCE自动注册的自动加载而被加载,这样当调用远程方法时,会因为本地已加载了而直接调用该方法,导致无法发起远程请求。

  • 错误示例
// 该类文件符合DCE自动加载,会直接调用该类方法而无法发起远程请求
// APP_COMMON . 'service/hello/HelloRemote.php'
namespace service\hello;

use dce\rpc\RpcMatrix;

class HelloRemote extends RpcMatrix {
    public static function hello(... $args) {
        return json_encode($args);
    }
}
1
2
3
4
5
6
7
8
9
10
11
  • 正确示例
// 该类不会自动加载,调用时会发起远程请求RPC服务端(服务端需先主动通过`->prepare或->preload`主动加载该类),服务端调用接口方法并将结果响应给客户端
// APP_COMMON . 'service/rpc/HelloRemote.php'
namespace rpc;

use dce\rpc\RpcMatrix;

class HelloRemote extends RpcMatrix {
    public static function hello(... $args) {
        return json_encode($args);
    }
}
1
2
3
4
5
6
7
8
9
10
11

# \dce\rpc\RpcClient

Rpc客户端类,拦截Rpc服务,发起远程调用再返回结果

# ::prepare()

拦截rpc命名空间的类,将其转到当前RPC客户端下的魔术方法处理

与服务端的区别是:服务端将RPC服务的名字空间注册到真实的类目录或文件,而客户端注册到回调函数,在该函数中访问服务端接口返回其结果。经过这个巧妙的小设计,让远程调用和本地调用形式完全一样,IDE的智能提示也完全可用,非常优雅。

  • 参数

    • array|string $wildcards 远程服务名字空间通配符
    • array $hosts 服务主机列表(DCE会根据这些主机创建连接池,而不是直接建立连接,配有多个主机时会自动负载均衡)
      • {host, port, token} token不填则表示非加密API,将不传递token,此map形式会自动转为下方的数组形式
      • [{host, port, token, max_connection}] 主机集形式,标准形式
  • 返回void

  • 示例

// 注册拦截
RpcClient::prepare(['rpc\http\*'], ['host' => self::LOCAL_API_HOST, 'port' => 0]);

// 调用远程服务
\rpc\http\HttpServerApi::status();
1
2
3
4
5

# ::preload()

拦截rpc类,将其转到当前RPC客户端下的魔术方法处理(与::prepare()不同的是本方法直接拦截类名而不是命名空间)

  • 参数

    • array|string $className 注册的需拦截类名
    • array $hosts::prepare()方法的该参数用法一致
  • 返回void

  • 示例

RpcClient::preload('\rpc\didg\IdgServerRpc', $serverHosts);
1

# ::with()

向特定的服务器请求远程方法(使用箭头函数将更方便,如$mid = RpcClient::with(fn() => rpc\service\MidGenerator::generation(), '127.0.0.1', '2333')

  • 参数

    • string $host 主机地址
    • int $port 主机端口
    • callable $callback 返回调用Rpc方法结果的匿名函数
  • 返回mixed

  • 示例

// 注册拦截
RpcClient::prepare(['rpc\*'], ['host' => self::LOCAL_API_HOST, 'port' => 0]);

// 调用指定主机的远程服务
$result = RpcClient::with(self::LOCAL_API_HOST, 0, fn() => \rpc\HelloRemote::hello(1, 2, 3));
1
2
3
4
5

# \dce\rpc\DceRpcLoader

DCE Rpc客户端自动注册工具类,本类无需主动调用,配置好rpcService后DCE会在启动时自动调用。

  • 示例
// config.php
// 在config中配置rpcService后,Dce将自动在守护进程中创建Rpc服务器
[
  'rpcService' => [
    'hosts' => [['host'=>'', 'port'=>0, 'password'=>'', 'ipWhiteList'=>['{{ip}}', ], 'needNative'=>false, 'needLocal'=>false], ],
    'prepares' => [['wildcard'=>'rpc\\*', 'root'=>'{{rootDir}}'], ],
    'preloads' => ['{{phpFile}}', ],
  ]
]

// 在config中配置rpcConnection后,Dce将自动按照配置注册Rpc服务,你无需手动调用RpcClient即可直接调用Rpc方法
'rpc_connection' => [
  [
    'hosts' => [
      ['host' => 'host1', 'port' => 'port1', 'token' => ''],
      ['host' => 'host2', 'port' => 'port2', ],
    ],
    'wildcards' => ['rpc\*', 'http\server\*'],
  ]
]

// [SOME_CONTROLLER->METHOD]()
// 直接在某控制器方法中调用远程接口方法
\rpc\SomeService::doJob();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 完整RPC示例

# 定义远程接口类

// APP_COMMON . 'service/rpc/RemoteApi.php'
namespace rpc;

use dce\rpc\RpcMatrix;

class RemoteApi extends RpcMatrix {
  public static function echo(... $arguments) {
    return sprintf("收到参数:%s", json_encode($arguments));
  }
}
1
2
3
4
5
6
7
8
9
10

# 启动服务

// [SOME_CONTROLLER->METHOD]()
RpcServer::new(RpcServer::host())
  ->addHost(RpcServer::host('0.0.0.0', 20469)->setAuth('drunk'))
  ->preload(APP_COMMON . 'service/rpc/RemoteApi.php')
  ->start();
1
2
3
4
5

# 客户端注册拦截并调用

// [SOME_CONTROLLER->METHOD]()
// 注册拦截
RpcClient::prepare(['rpc\*'], ['host' => '127.0.0.1', 'port' => 0]);

// 调用远程服务
$result = \rpc\RemoteApi::echo(1, 2, 3);
test($result);
// 收到参数:[1,2,3]
1
2
3
4
5
6
7
8