RPC 简单小试

时间:2022-11-30 11:58:57

 由于经常被抓取文章内容,在此附上博客文章网址:,偶尔会更新某些出错的数据或文字,建议到我博客地址 :  --> ​​点击这里​​

  RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
百度中有很多种解释,但根据公司所用的,以下解释比较贴切:
        首先,要解决通讯的问题,主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
        第二,要解决寻址的问题,也就是说,A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI,或者是从UDDI服务上查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。
        第三,当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制发送给B服务器。
        第四,B服务器收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中的表达方式,然后找到对应的方法(寻址的一部分)进行本地调用,然后得到返回值。
        第五,返回值还要发送回服务器A上的应用,也要经过序列化的方式发送,服务器A接到后,再反序列化,恢复为内存中的表达方式,交给A服务器上的应用

RPC 简单小试

公司所用的是客户端为php,服务端为lua。


举例(只是两个简单的示例,实际项目比较复杂):

服务端示例:

<?php
class RpcServer {
protected $serv = null;

public function __construct($host, $port, $path) {
//创建一个tcp socket服务
$this->serv = stream_socket_server("tcp://{$host}:{$port}", $errno, $errstr);
if (!$this->serv) {
exit("one {$errno} : {$errstr} \n");
}
//判断我们的RPC服务目录是否存在
$realPath = realpath(__DIR__ . $path);

if ($realPath === false || !file_exists($realPath)) {
exit("two {$path} error \n");
}

while (true) {
$client = stream_socket_accept($this->serv);

if ($client) {
//这里为了简单,我们一次性读取
$buf = fread($client, 2048);
//解析客户端发送过来的协议
$classRet = preg_match('/Rpc-Class:\s(.*);\r\n/i', $buf, $class);
$methodRet = preg_match('/Rpc-Method:\s(.*);\r\n/i', $buf, $method);
$paramsRet = preg_match('/Rpc-Params:\s(.*);\r\n/i', $buf, $params);

if($classRet && $methodRet) {
$class = ucfirst($class[1]);
$file = $realPath . '/' . $class . '.php';
//判断文件是否存在,如果有,则引入文件
if(file_exists($file)) {
require_once $file;
//实例化类,并调用客户端指定的方法
$obj = new $class();
//如果有参数,则传入指定参数
if(!$paramsRet) {
$data = $obj->$method[1]();
} else {
$data = $obj->$method[1](json_decode($params[1], true));
}
//把运行后的结果返回给客户端
fwrite($client, $data);
}
} else {
fwrite($client, 'class or method error' . $classRet . '-' . $methodRet);
}
//关闭客户端
fclose($client);
}
}
}

public function __destruct() {
fclose($this->serv);
}
}

new RpcServer('127.0.0.1', 8888, '/service');

客户端举例:

<?php

class RpcClient {
protected $urlInfo = array();

public function __construct($url) {
//解析URL
$this->urlInfo = parse_url($url);
if(!$this->urlInfo) {
exit("{$url} error \n");
}
}

public function __call($method, $params) {
//创建一个客户端
$client = stream_socket_client("tcp://{$this->urlInfo['host']}:{$this->urlInfo['port']}", $errno, $errstr);
if (!$client) {
exit("{$errno} : {$errstr} \n");
}
//传递调用的类名
$class = basename($this->urlInfo['path']);
$proto = "Rpc-Class: {$class};" . 'Say';
//传递调用的方法名
$proto .= "Rpc-Method: {$method};" . 'hello';
//传递方法的参数
$params = json_encode($params);
$proto .= "Rpc-Params: {$params};" . PHP_EOL;
//向服务端发送我们自定义的协议数据
fwrite($client, $proto);
//读取服务端传来的数据
$data = fread($client, 2048);
//关闭客户端
fclose($client);
return $data;
}
}

$cli = new RpcClient('http://127.0.0.1:8888/test');
echo $cli->hehe();
echo $cli->hehe2(array('name' => 'test', 'age' => 27));

 先启动服务端,再启动客户端



源码面前,了无秘密