RPC英文名称Remote Procedure Call,翻译过来为远程过程调用。主要应用于不同编程语言、不同系统之间的远程通信和相互调用。
RPC具体如下优点:
支持多种通信协议,如http、tcp
支持同步调用和异步调用
如同调用自身系统一样方便
下面以PHP为例简单实现RPC服务
整个过程如下:
服务端启动RPC进程,阻塞等待客户端连接
客户端通过TCP与服务端建立socket连接
客户端将要调用的类、方法、参数以json的格式传给服务端
服务端接收到参数后调用相应的类的方法,然后将结果以json格式返回给客户端
实现代码如下:
RpcServer.php
class RpcServer
{
protected $params = [];
protected $defaultHost = '0.0.0.0';
protected $server;
protected $successCode = 0;
protected $errorCode = 1;
protected $successMsg = 'success';
protected $bufSize = 4096;
public $errorNo = 0;
public $errorStr = '';
public function __construct($params)
{
$this->params = $params;
$this->checkParams();
$this->createServer();
}
/**
* 检测参数
* @throws Exception
*/
protected function checkParams()
{
if (!isset($this->params['host']) || empty($this->params['host'])) {
$this->params['host'] = $this->defaultHost;
}
if (!isset($this->params['port']) || empty($this->params['port'])) {
throw new Exception("port is empty");
}
if (!isset($this->params['path']) || empty($this->params['path'])) {
throw new Exception('params path is empty');
} elseif (!is_dir($this->params['path'])) {
throw new Exception("path is not a dir");
}
}
protected function createServer()
{
$host = $this->params['host'];
$port = $this->params['port'];
$errorStr = '';
$errorNo = 0;
$this->server = stream_socket_server("tcp://{$host}:{$port}", $errorNo, $errorStr);
if ($this->server === false) {
throw new Exception('stream_socket_server error:'. $errorNo. "\t" . $errorStr);
}
echo "create server success...\n";
}
/**
*
*/
public function run()
{
while (true) {
$client = @stream_socket_accept($this->server);
if ($client) {
echo "accept...\n";
$buf = '';
while (true) {
$res = stream_socket_recvfrom($client, $this->bufSize);
$buf .= $res;
if (strlen($res) < $this->bufSize) {
break;
}
}
echo "receive data: ".$buf."\n";
$params = $this->parseParams($buf);
if ($params === false) {
$result = [
'code' => $this->errorNo,
'msg' => $this->errorStr,
];
} else {
$execResult = $this->exec($params);
if ($execResult === false) {
$result = [
'code' => $this->errorNo,
'msg' => $this->errorStr,
];
} else {
$result = [
'code' => $this->successCode,
'msg' => $this->successMsg,
'data' => $execResult,
];
}
}
$result = json_encode($result, JSON_UNESCAPED_UNICODE);
echo "return data: ".$result;
fwrite($client, $result);
fclose($client);
}
}
}
/**
* 执行操作
* @param $params
* @return bool|mixed
*/
protected function exec($params)
{
try {
$className = isset($params['class']) ? $params['class'] : '';
$method = isset($params['method']) ? $params['method'] : '';
if (empty($className)) {
$this->errorStr = 'class is empty';
$this->errorNo = $this->errorCode;
return false;
} elseif (empty($method)) {
$this->errorStr = 'method is empty';
$this->errorNo = $this->errorCode;
return false;
}
$class = $this->params['path'].$className.'.php';
if (!file_exists($class)) {
$this->errorStr = "file {$class} is not exist";
$this->errorNo = $this->errorCode;
return false;
}
include_once $class;
if (!class_exists($className)) {
$this->errorStr = "class {$className} is not exist";
$this->errorNo = $this->errorCode;
return false;
}
$obj = new ReflectionClass($className);
if (!$obj->hasMethod($method)) {
$this->errorNo = $this->errorCode;
$this->errorStr = "{$className} is not has method {$method}";
return false;
}
$methodObj = new \ReflectionMethod($className, $method);
if (!$methodObj->isPublic()) {
$this->errorStr = "method {$method} is not public";
$this->errorNo = $this->errorCode;
return false;
}
$instance = $obj->newInstance();
return $methodObj->invokeArgs($instance, $params['params']);
} catch (Exception $e) {
echo $e->getFile()."\n";
echo $e->getLine()."\n";
echo $e->getMessage()."\n";
$this->errorNo = $e->getCode();
$this->errorStr = $e->getMessage();
}
return false;
}
/**
* 解析参数
* @param array $params
* @return array|bool
*/
protected function parseParams($params)
{
$result = ['method' => '', 'class' => '', 'params' => []];
if (empty($result)) {
$this->errorStr = "参数不合法";
$this->errorNo = $this->errorCode;
return false;
}
$data = json_decode($params, true);
if (json_last_error()) {
$this->errorStr = json_last_error_msg();
$this->errorNo = json_last_error();
return false;
}
if (empty($data) || !is_array($data)) {
$this->errorStr = "参数为空或格式不合法";
$this->errorNo = $this->errorCode;
return false;
}
return $data;
}
}RpcClient.php
class RpcClient
{
protected $params;
protected $bufSize = 4096;
/**
* RpcClient constructor.
* @param $params
* host rpc服务地址
* port rpc端口
*/
public function __construct($params)
{
$this->params = $params;
}
/**
* @return bool|resource
* @throws Exception
*/
protected function getClient()
{
$host = $this->params['host'];
$port = $this->params['port'];
$errorStr = '';
$errorNo = 0;
$client = stream_socket_client("tcp://{$host}:{$port}", $errorNo, $errorStr);
if ($client === false) {
throw new Exception('stream_socket_client error:'. $errorNo. "\t" . $errorStr);
}
return $client;
}
public function call($params)
{
try {
$client = $this->getClient();
$raw = json_encode($params, JSON_UNESCAPED_UNICODE);
$raw = ($raw);
fwrite($client, $raw);
$result = '';
while (true) {
$buf = fread($client, $this->bufSize);
$result .= $buf;
if (strlen($buf) < $this->bufSize) {
break;
}
}
fclose($client);
return $result;
} catch (Exception $e) {
echo $e->getFile()."\n";
echo $e->getLine()."\n";
echo $e->getMessage()."\n";
}
}
}测试:
在当前目录中建一个class目录用于存放RPC客户端调用的类,如
Hello.php
class Hello
{
public function test($a, $b, $c) {
return "a={$a},b={$b},c={$c}";
}
}在当前目录中分别建ServerTest.php和Client.php用于启动服务端与客户端进行测试:
ServerTest.php
include 'RpcServer.php'; include 'class/Hello.php'; $class = 'Hello'; $method = 'test'; $p = [ 'c' => 2, 'b' => 1, 'a' => 3, ]; $parmas = [ 'host' => '0.0.0.0', 'port' => '1234', 'path' => realpath(__DIR__)."/class/", ]; $server = new RpcServer($parmas); $server->run();
ClientTest.php
$params = [ 'host' => '127.0.0.1', 'port' => '1234', 'path' => realpath(__DIR__)."/class/", ]; $client = new RpcClient($params); $params = [ 'class' => 'Hello', 'method' => 'test', 'params' => ['b' => 1, 'a' => '2', 'c' => time()], ]; $res = $client->call($params); print_r($res);
启动一个终端运行ServerTest.php,结果如下:
$ php ServerTest.php create server success...
然后启动另一终端运行ClientTest.php
$ php ClientTest.php
{"code":0,"msg":"success","data":"a=1,b=2,c=1572361547"}其中第二行中内容为RPC服务端返回的内容,同时看到第一个终端中有如下内容输出:
receive data: {"class":"Hello","method":"test","params":{"b":1,"a":"2","c":15723
61547}}
return data: {"code":0,"msg":"success","data":"a=1,b=2,c=1572361547"}至于,客户端可正常调用RPC服务端并进行通信。
