什么是RPC
RPC协议(Remote Procedure Call)就是远程过程调用,即被调用的方法的具体实现不在本地,而是在其他地方。主要应用于不同系统或服务器之间的远程通信和相互调用。RPC假定某些传输协议是存在的,如TCP/UDP,在OSI中,RPC位于第五层会话层
,跨过了传输层和应用层,使得包括网络分布式多程序在内的应用程序更加容易。
RPC协议主要采用C/S模式。客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
RPC的工作流程
- 客户端程序调用rpc客户端句柄,指定相关参数
- client stub底层调用TCP/UDP在网络间进行传输消息
- 消息传递到远程主机上
- rpc server stub从socket中捕获传送的消息并取得参数
- 执行远程调用
- 执行过程将结果返回给server stub
- stub返回结果,调用底层网络通信协议
- 结果消息传递到调用服务器
- client stub接收消息
- client stub返回执行结果
RPC的优点
- 传输协议:可以基于TCP协议,也可以基于HTTP协议
- 传输效率:使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以很好的减少报文的体积,提高传输效率
- 性能消耗:可以基于thrift实现高效的二进制传输
- 负载均衡:基本都自带了负载均衡策略
- 服务治理:能做到自动通知,不影响上游
简单实现(PHP)
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;
}
}