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服务端并进行通信。
