前面我会说一下rpc,然后再说一下我搭建的步骤,可能文章会比较长,如果你的环境已经搭建好了,可以移动到最底部,看客户端、服务端、路由的代码。就可以了,但前提你要保证你完成了这些步骤:
1、composer下载
2、生成两个配置文件 route/rpc.php、config/hprose
3、.env 编写监听的端口和采用的通讯协议
4、在route/rpc.php中编写路由
5、编写路由对应的逻辑方法
6、服务端可以监听端口了
RPC
RPC(Remote Procedure Call Protocol)——远程过程调用协议,RPC将原来的本地调用转变为调用远端的服务器上的方法,给系统的处理能力和吞吐量带来了近似于无限制提升的可能。在OSI网络通信模型中,RPC跨域了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC架构
一个完整的RPC架构里面包含了四个核心的组件,分别是Client,Client Stub,Server以及Server Stub,这个Stub可以理解为存根。
- 客户端(Client),服务的调用方。
- 客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
- 服务端(Server),真正的服务提供者。
- 服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。
RPC调用过程
(1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;
(2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);
(3) 客户端通过sockets将消息发送到服务端;
(4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);
(5) 服务端存根( server stub)根据解码结果调用本地的服务;
(6) 本地服务执行并将结果返回给服务端存根( server stub);
(7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);
(8) 服务端(server)通过sockets将消息发送到客户端;
(9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);
(10) 客户端(client)得到最终结果。
RPC的目标是要把2、3、4、7、8、9这些步骤都封装起来。
注意:无论是何种类型的数据,最终都需要转换成二进制流在网络上进行传输,数据的发送方需要将对象转换为二进制流,而数据的接收方则需要把二进制流再恢复为对象。
laravel中使用RPC
具体文档:
官方:https://github.com/hprose/hprose-php/wiki/05-Hprose-%E5%AE%A2%E6%88%B7%E7%AB%AF
laravel框架的:https://github.com/zhuqipeng/laravel-hprose
要求:
php >= 5.0
laravel / lumen >= 5.2
demo
先切换镜像到阿里云再执行下载 命令如下:
composer config repo.packagist composer https://mirrors.aliyun.com/composer/
composer require "zhuqipeng/laravel-hprose:v1.0-alpha" --ignore-platform-reqs -vvv
遇到两个错误
第一个错误,执行下载rpc包命令时候
Fatal error: Class UpdateHelper\ComposerPlugin contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Composer\Plugin\PluginInterface::deactivate, Composer\Plugin\PluginInterface::uninstall) in /Users/twj/Documents/nginx_www/more/mhhf_act/vendor/kylekatarnls/update-helper/src/UpdateHelper/ComposerPlugin.php on line 11
初步怀疑是 你的laravel框架中 vendor/kylekatarnls的版本问题 (当然也可能其他包也存在版本问题也不排除,我这里是vendor/kylekatarnls的版本太低了)
第一种解决方案是删除再重新执行下载rpc包的命令,但是记得做好备份再删除!!!!!!!
第二种就是在你另一个laravel项目中把vendor/kylekatarnls这个包复制粘贴进去,当然也要做好备份哦,免得找不回来旧包而导致别的错误
我提升了包的版本后,开始遇到第二个错误
In PackageManifest.php line 120:
Undefined index: name
Script @php artisan package:discover handling the post-autoload-dump event returned with error code 1
Installation failed, reverting ./composer.json and ./composer.lock to their original content.
这个错误的源码我找到是在这里的120行
mhhf_act/vendor/laravel/framework/src/Illuminate/Foundation/PackageManifest.php
此时,进入这个方法 : mapWithKeys
修改他的源码,具体是什么原因,你们打印一下就知道了。
修改后的样子:
/**
* Run an associative map over each of the items.
*
* The callback should return an associative array with a single key/value pair.
*
* @param callable $callback
* @return static
*/
public function mapWithKeys(callable $callback)
{
$result = [];
$item = $this->items;
if(isset($item['packages'])){
$item = $item['packages'];
}
foreach ($item as $key => $value) {
$assoc = $callback($value, $key);
foreach ($assoc as $mapKey => $mapValue) {
$result[$mapKey] = $mapValue;
}
}
return new static($result);
}
好,现在两个错误都解决了,再次执行composer下载rpc包
composer require "zhuqipeng/laravel-hprose:v1.0-alpha" --ignore-platform-reqs -vvv
开始步骤
1、配置文件
执行命令:
php artisan vendor:publish --provider="Zhuqipeng\LaravelHprose\ServiceProvider"
会在应用目录下创建
/config/hprose.php
/routes/rpc.php
输入命令后如果提示这样,说明是成功的:
php artisan vendor:publish --provider="Zhuqipeng\LaravelHprose\ServiceProvider"
Copied File [/vendor/zhuqipeng/laravel-hprose/src/config.php] To [/config/hprose.php]
Copied File [/vendor/zhuqipeng/laravel-hprose/src/route.php] To [/routes/rpc.php]
Publishing complete.
2、.env配置
#rpc
HPROSE_URIS=["tcp://www.mhhfact.net:1314"]//监听地址列表,字符串json格式数组
HPROSE_URL="tcp://www.mhhfact.net:1314"//远端请求地址,不是json格式数组的
HPROSE_DEMO="true"// 是否启用demo方法,true开启 false关闭,开启后将自动对外发布一个远程调用方法 demo
服务端
在生成的文件中(/routes/rpc.php) 编写路由代码
我这里是用laravel的中间件,因为我发现好像这个包没有提供中间件,也没事,我们用laravel自带的就好。
<?php
// rpc路由 -> 测试调试
\LaravelHproseRouter::add('demo', function () {
return 'demo';
});
// rpc路由 -> 测试调试 $name 为函数的参数
\LaravelHproseRouter::add('getUserByName', function ($name) {
return 'name: ' . $name;
});
// rpc路由 -> 测试调试
\LaravelHproseRouter::add('userUpdate', \App\Http\Controllers\Api\v1\Rpc\RpcServiceApi::class . '@update', ['model' => \Hprose\ResultMode::Normal]);
// 开始正式请求
Route::group(['prefix' => 'v1'], function ($router) {
$router->group(['middleware' => ['api.sign.auth']], function () {
/**
* Hprose\ResultMode::Normal 是默认值,表示返回正常的已被反序列化的结果。
* Hprose\ResultMode::Serialized 表示返回的结果保持序列化的格式。
* Hprose\ResultMode::Raw 表示返回原始数据。
* Hprose\ResultMode::RawWithEndTag 表示返回带有结束标记的原始数据。
*/
// rpc路由 -> 应用到我的项目代码
\LaravelHproseRouter::add(
'ticketWriteOffList',
\App\Http\Controllers\Api\v1\Rpc\RpcActivityApi::class . '@getDisplayWindow',
['model' => \Hprose\ResultMode::Normal]
);
// rpc路由 -> 应用到我的项目代码
\LaravelHproseRouter::add(
'updateUserTake',
\App\Http\Controllers\Api\v1\Rpc\RpcWithdrawalApi::class . '@updateUserTake',
['model' => \Hprose\ResultMode::Normal]
);
});
});
服务端 提供的方法 userUpdate 方法对应的路由叫userUpdate:
服务端 提供的方法getDisplayWindow。方法对应的路由也叫getDisplayWindow: 他返回的是一个商品的详情 是一个array 后面我会给大家看他的数据返回是什么样子的
<?php
namespace App\Http\Controllers\Api\v1\Rpc;
use App\Http\Controllers\Controller;
use App\Services\ActivityService;
class RpcActivityApi extends Controller
{
/***
* rpc 通讯 -> 获取线下活动详情
* @param int $act_id 活动id
* @return array
*/
public function getDisplayWindow($act_id) : array
{
return ActivityService::getDisplayWindow($act_id);
}
}
步骤:
1、composer下载
2、生成两个配置文件 route/rpc.php、config/hprose
3、.env 编写监听的端口和采用的通讯协议
4、在route/rpc.php中编写路由
5、编写路由对应的逻辑方法
当你完成了这五个步骤之后,就可以去终端启动监听了。
监听命令如下:
twj@tongwenjiedeMacBook-Pro mhhf_act % php artisan hprose:socket_server
客户端请求开始
这里采用 getDisplayWindow 这个远端方法来进行做例子
客户端的代码如下:
/**
* 线下活动商品
* @param $act_id
* @return array
*/
public static function getAct($act_id)
{
try {
// http://www.mhhfact.net 本地
// http://qa.act.meilife365.com 测试
// $url = env('APP_DEBUG')
// ? 'http://qa.act.meilife365.com' // 我本地的测试环境
// : 'http://pc-act.meilife365.com'; // 正式环境
// $withData = ['act_id' => $act_id];
// $res = Curl::to($url . '/api/v1/get/display/window')
// ->withHeaders(CommonService::headerSignArr())
// ->withData($withData)
// ->get();
// $data = json_decode($res, true);
// if (!isset($data['data']['act_id']) || !$data['data']['act_id']) {
// throw new \Exception('没有该线上营,或没有绑定sku', -1);
// }
// $insert = [
// 'value' => $data['data']['act_id'] ?: 0,
// 'type' => 3,
// 'name' => $data['data']['title'] ?: '',
// 'price' => $data['data']['price'] ?: 0,
// 'cover_img' => $data['data']['cover'] ?: '',
// 'status' => 1,
// 'course_platform' => 2,
// ];
// return $insert;
// rpc 通讯
$clinet = new Client(env('HPROSE_URL'), false);
$insert = $clinet->getDisplayWindow($act_id);
if (!$insert) {
throw new \Exception('暂无线下活动', -1);
}
return $insert;
} catch (\Exception $e) {
Log::info('获取线下活动失败', [
'信息' => $e->getMessage(),
'文件' => $e->getFile(),
'行数' => $e->getLine()
]);
return [];
}
}
给大家看看传统的http请求别的项目是多么的繁琐
代码上的区别
数据上的区别
总结:
1、rpc就像调用本地函数一样,不像http还要封装请求头,请求方法,数据包自然就小,适合大项目,小项目就体验不到什么速度了
2、rpc可以不用json_decode 、 json_encode 之类的交互了
3、rpc还能异步调用
4、rpc 适合微服务、项目之间关联使用。
5、rpc启动监听之后,是在内存常驻的,会消耗内存cpu,但是速度会快。
6、远端都不需要定义返回规则了,比如code、data、msg之类的,因为rpc的调用就像调用本地函数一样。
后续你肯定要守护进程的
systemd、nohup、supervisor 都可以做守护 php artisan hprose:socket_server 这一条命令
如果你想要自己手动测试,那你复制我下面的代码就可以了,上面只是讲解我的心得
首先是你环境、配置都弄好了之后,就复制这些代码
路由代码:
<?php
\LaravelHproseRouter::add('demo', function () {
return 'demo';
});
\LaravelHproseRouter::add('getUserByName', function ($name) {
return 'name: ' . $name;
});
\LaravelHproseRouter::add('userUpdate', \App\Http\Controllers\Api\v1\Rpc\RpcServiceApi::class . '@update', ['model' => \Hprose\ResultMode::Normal]);
客户端代码
<?php
namespace App\Http\Controllers\Api\v1\Rpc;
use App\Http\Controllers\Controller;
class RpcClientApi extends Controller
{
/**
* demo 客户端
* @return mixed
*/
public function index()
{
$client = new \Hprose\Socket\Client(env('HPROSE_URL'), false);
$data = $client->userUpdate(15);
return $data;
}
}
服务端代码
<?php
namespace App\Http\Controllers\Api\v1\Rpc;
use App\Http\Controllers\Controller;
class RpcServiceApi extends Controller
{
/**
* demo 服务端
* @param $name
* @return string
*/
public function update($name)
{
return 'update name: ' . $name;
}
}
服务端监听端口
php artisan hprose:socket_server
客户端请求即可