什么是RPC

RPC协议(Remote Procedure Call)就是远程过程调用,即被调用的方法的具体实现不在本地,而是在其他地方。主要应用于不同系统或服务器之间的远程通信和相互调用。RPC假定某些传输协议是存在的,如TCP/UDP,在OSI中,RPC位于第五层会话层,跨过了传输层和应用层,使得包括网络分布式多程序在内的应用程序更加容易。
RPC协议主要采用C/S模式。客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。

RPC的工作流程

rpc属于哪一层 rpc工作在哪一层_php

  1. 客户端程序调用rpc客户端句柄,指定相关参数
  2. client stub底层调用TCP/UDP在网络间进行传输消息
  3. 消息传递到远程主机上
  4. rpc server stub从socket中捕获传送的消息并取得参数
  5. 执行远程调用
  6. 执行过程将结果返回给server stub
  7. stub返回结果,调用底层网络通信协议
  8. 结果消息传递到调用服务器
  9. client stub接收消息
  10. client stub返回执行结果

RPC的优点

  • 传输协议:可以基于TCP协议,也可以基于HTTP协议
  • 传输效率:使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以很好的减少报文的体积,提高传输效率
  • 性能消耗:可以基于thrift实现高效的二进制传输
  • 负载均衡:基本都自带了负载均衡策略
  • 服务治理:能做到自动通知,不影响上游

简单实现(PHP)

rpc属于哪一层 rpc工作在哪一层_TCP_02

RpcServer
<?php

class RpcServer {

    private $params = [
        'host'  => '',  // ip地址,列出来的目的是为了友好看出来此变量中存储的信息
        'port'  => '', // 端口
        'path'  => '' // 服务目录
    ];


    private $config = [
        'real_path' => '',
        'max_size'  => 2048 // 最大接收数据大小
    ];


    private $server = null;


    private function __construct($params)
    {
        $this->check();
        $this->init($params);
    }

    private function __clone()
    {
        // TODO: Implement __clone() method.
    }

    private function check() {
        $this->serverPath();
    }


    private function init($params) {
        $this->params = $params;
        $this->createServer();
    }


    private function createServer() {
        $this->server = stream_socket_server("tcp://{$this->params['host']}:{$this->params['port']}", $errno,$errstr);
        if (!$this->server) exit([
            $errno,$errstr
        ]);
    }


    public function serverPath() {
        $path = $this->params['path'];
        $realPath = realpath(__DIR__ . $path);
        if ($realPath === false ||!file_exists($realPath)) {
            exit("{$path} error!");
        }
        $this->config['real_path'] = $realPath;
    }


    public static function getInstance($params) {
        return new RpcServer($params);
    }


    public function run() {
        while (true) {
            $client = stream_socket_accept($this->server);
            if ($client) {
                echo "have a new connect\n";
                $buf = fread($client, $this->config['max_size']);
                print_r('接收到的原始数据:'.$buf."\n");
                // 自定义协议目的是拿到类方法和参数(可改成自己定义的)
                $this->parseProtocol($buf,$class, $method,$params);
                // 执行方法
                $this->execMethod($client, $class, $method, $params);
                //关闭客户端
                fclose($client);
                echo "closed the connect\n";
            }
        }
    }


    private function execMethod($client, $class, $method, $params) {
        if(!empty($class) && !empty($method)) {
            // 首字母转为大写
            $class = ucfirst($class);
            $filePath = $this->params['path'] . DIRECTORY_SEPARATOR . $class . '.php';
            //判断文件是否存在,如果有,则引入文件
            if(file_exists($filePath)) {
                require_once $filePath;
                $workObjClass = new ReflectionClass($class);
                if ($workObjClass->hasMethod($method)) {
                    $methodTmp = $workObjClass->getMethod($method);
                    if ($methodTmp->isPublic()) {
                        $data = $methodTmp->invoke($workObjClass->newInstance($params));
                        $this->packProtocol($data);
                        fwrite($client, $data);
                    } else {
                        fwrite($client, "the {$method} METHOD is not public");
                    }
                } else {
                    fwrite($client,"the {$class} CLASS is not the {$method} METHOD");
                }
            } else {
                fwrite($client,"{$class} file is not exist");
            }
        } else {
            fwrite($client, 'class or method error');
        }
    }


    private function parseProtocol($buf, &$class, &$method, &$params) {
        $buf = json_decode($buf, true);
        $class = $buf['class'];
        $method = $buf['method'];
        $params = $buf['params'];
    }

    private function packProtocol(&$data) {
        $data = json_encode($data, JSON_UNESCAPED_UNICODE);
    }

}

RpcServer::getInstance([
    'host'  => '127.0.0.1',
    'port'  => 8888,
    'path'  => './api'
])->run();
RpcClient
<?php

class RpcClient {


    private $urlInfo = array();

    private $config = [
        'max_size' => 2048
    ];

    public function __construct($url)
    {
        $this->urlInfo = parse_url($url);
    }


    public static function instance($url) {
        return new RpcClient($url);
    }

    public function __call($name, $arguments)
    {
        // TODO: Implement __call() method.
        //创建一个客户端
        $client = stream_socket_client("tcp://{$this->urlInfo['host']}:{$this->urlInfo['port']}", $errno, $errstr);
        if (!$client) {
            exit("{$errno} : {$errstr} \n");
        }
        $data = [
            'class'  => basename($this->urlInfo['path']),
            'method' => $name,
            'params' => !empty($arguments) ? $arguments : []
        ];
        //向服务端发送我们自定义的协议数据
        fwrite($client, json_encode($data));
        $data = fread($client, $this->config['max_size']);
        fclose($client);
        return $data;
    }
}
$cli = new RpcClient('http://127.0.0.1:8888/test');
echo $cli->test1()."\n";
echo $cli->test2(array('name' => 'lishanlei', 'age' => 21)) . "\n";
Test
<?php

class Test {
    private $params;
    public function __construct($params)
    {
        $this->params = $params[0];
    }

    public function test1() {
        return '我是无参方法';
    }
    public function test2() {
        $test = "my name is {$this->params['name']}, my age is {$this->params['age']}";
        return $test;
    }
}