gRPC实现RPC服务-PHP客户端nodejs服务端
目录
环境条件
-
PHP 7.0 +
-
PECL
sudo apt-get install php7.0 php7.0-dev php-pear phpunit
或
sudo apt-get install php php-dev php-pear phpunit
-
Composer
$ curl -sS https://getcomposer.org/installer | php $ sudo mv composer.phar /usr/local/bin/composer
-
PHPUnit (可选)
$ wget https://phar.phpunit.de/phpunit-old.phar $ chmod +x phpunit-old.phar $ sudo mv phpunit-old.phar /usr/bin/phpunit
安装gRPC扩展
gRPC扩展有两种安装方式:pecl直接安装,也可以通过phpize进行源码编译安装。
-
通过pecl安装gRPC扩展
sudo pecl install grpc
或指定版本
sudo pecl install grpc-1.7.0
吐槽:install等的不耐烦了,新闻一条一条的看完了,还在闪烁着满屏的字母…
总算好了:
Build process completed successfully Installing '/usr/lib/php/20160303/grpc.so' install ok: channel://pecl.php.net/grpc-1.36.0 configuration option "php_ini" is not set to php.ini location You should add "extension=grpc.so" to php.ini
-
通过源码安装gRPC扩展
-
centos只能通过源码安装
git clone -b v1.35.0 https://github.com/grpc/grpc
-
构建及安装gRPC核心库
$ cd grpc $ git submodule update --init $ make $ sudo make install
-
构建并安装gRPC PHP扩展
编译gRPC PHP扩展
$ cd grpc/src/php/ext/grpc $ phpize $ ./configure $ make $ sudo make install
这会将gRPC PHP扩展编译并安装到标准PHP扩展目录中。
-
-
更新php.ini
-
更新php.ini
extension=grpc.so
-
将gRPC PHP库添加为Composer依赖项
您需要将此添加到项目的composer.json文件中。
"require": { "grpc/grpc": "v1.7.0" }
-
Protobuf运行时库
Protobuf有两种安装方式:pecl扩展方式和composer包方式。
-
C实现方式
$ sudo pecl install protobuf
或指定
$ sudo pecl install protobuf-3.4.0
扩展添加到php.ini
extension=protobuf.so
-
php实现方式
将此添加到您的composer.json文件:
"require": { "google/protobuf": "^v3.3.0" }
PHP Protoc插件
gRPC PHP protoc插件来生成客户端stub classes。它可以根据.proto服务定义生成服务器和客户端代码。
插件可能会使用新的protobuf版本的新功能,因此也请确保安装的protobuf版本与您构建此插件的grpc版本兼容。
$ git clone -b v1.35.0 https://github.com/grpc/grpc
$ cd grpc
$ git submodule update --init
$ mkdir -p cmake/build
$ pushd cmake/build
$ cmake ../..
$ make protoc grpc_php_plugin
$ make install
$ popd
注意:看官方在quick start章节中Protobuf的安装文档是有误的,以basics tutorial章节的为准(github issue提出)。另外注意在make protoc grpc_php_plugin
之后没有生成protoc执行文件的话,可以make install
生成相关执行文件。
官方示例
.proto服务定义
.proto文件用于定义服务,并用于生成服务stub classes。
比如官方示例中:
cd grpc/examples/protos
protos/
├── auth_sample.proto
├── BUILD
├── hellostreamingworld.proto
├── helloworld.proto
├── keyvaluestore.proto
├── README.md
└── route_guide.proto
这里我们看下helloworld.proto的rpc定义:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
生成gRPC代码
生成的代码用于rpc客户端。
$ cd examples/php
$ ls -a
composer.json echo .gitignore greeter_client.php greeter_proto_gen.sh README.md route_guide run_greeter_client.sh
./greeter_proto_gen.sh
看下生成代码的greeter_proto_gen.sh文件内容:
#protoc --proto_path=../protos --php_out=. --grpc_out=. --plugin=protoc-gen-grpc=../../bins/opt/grpc_php_plugin ../protos/helloworld.proto
protoc --proto_path=../protos --php_out=. --grpc_out=. --plugin=protoc-gen-grpc=../../cmake/build/grpc_php_plugin ../protos/helloworld.proto
注意:shell文件中指定了../protos/helloworld.proto服务文件,根据该服务文件生成相应的stub classess。
在当前目录下生成 HelloWorld 目录:
$ tree ./Helloworld/
./Helloworld/
├── GreeterClient.php
├── HelloReply.php
└── HelloRequest.php
GreeterClient.php
namespace Helloworld;
/**
* The greeting service definition.
*/
class GreeterClient extends \Grpc\BaseStub {
/**
* @param string $hostname hostname
* @param array $opts channel options
* @param \Grpc\Channel $channel (optional) re-use channel object
*/
public function __construct($hostname, $opts, $channel = null) {
parent::__construct($hostname, $opts, $channel);
}
/**
* Sends a greeting
* @param \Helloworld\HelloRequest $argument input argument
* @param array $metadata metadata
* @param array $options call options
* @return \Grpc\UnaryCall
*/
public function SayHello(\Helloworld\HelloRequest $argument,
$metadata = [], $options = []) {
return $this->_simpleRequest('/helloworld.Greeter/SayHello',
$argument,
['\Helloworld\HelloReply', 'decode'],
$metadata, $options);
}
}
composer安装grpc与protobuf包:
$ composer install
Loading composer repositories with package information
Updating dependencies
Lock file operations: 2 installs, 0 updates, 0 removals
- Locking google/protobuf (v3.15.7)
- Locking grpc/grpc (1.36.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
- Downloading google/protobuf (v3.15.7)
- Downloading grpc/grpc (1.36.0)
- Installing google/protobuf (v3.15.7): Extracting archive
- Installing grpc/grpc (1.36.0): Extracting archive
Generating autoload files
运行RPC服务器
从examples/node目录:
$ npm install
$ cd dynamic_codegen
$ node greeter_server.js
greeter_servier.js
var PROTO_PATH = __dirname + '/../../protos/helloworld.proto';
var grpc = require('@grpc/grpc-js');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
/**
* Implements the SayHello RPC method.
*/
function sayHello(call, callback) {
callback(null, {message: 'Hello ' + call.request.name});
}
/**
* Starts an RPC server that receives requests for the Greeter service at the
* sample server port
*/
function main() {
var server = new grpc.Server();
server.addService(hello_proto.Greeter.service, {sayHello: sayHello});
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
server.start();
});
}
main();
执行RPC客户端
在另一个终端的examples/php目录中,运行客户端
$ ./run_greeter_client.sh
输出:
grpc/examples/php$ ./run_greeter_client.sh
PHP Warning: Module 'grpc' already loaded in Unknown on line 0
Hello world
我们看run_greeter_client.sh的文件内容:
set -e
cd $(dirname $0)
php -d extension=grpc.so -d max_execution_time=300 \
greeter_client.php $1
再看被执行的业务逻辑代码greeter_client.php:
require dirname(__FILE__).'/vendor/autoload.php';
function greet($hostname, $name)
{
$client = new Helloworld\GreeterClient($hostname, [
'credentials' => Grpc\ChannelCredentials::createInsecure(),
]);
$request = new Helloworld\HelloRequest();
$request->setName($name);
list($response, $status) = $client->SayHello($request)->wait();
if ($status->code !== Grpc\STATUS_OK) {
echo "ERROR: " . $status->code . ", " . $status->details . PHP_EOL;
exit(1);
}
echo $response->getMessage() . PHP_EOL;
}
$name = !empty($argv[1]) ? $argv[1] : 'world';
$hostname = !empty($argv[2]) ? $argv[2] : 'localhost:50051';
greet($hostname, $name);
其实我们已经在cli模式下php.ini中增加了grpc.so扩展,完全可以直接执行业务逻辑php脚本(rpc客户端):
grpc/examples/php$ php greeter_client.php 9ong.com
Hello 9ong.com
更新gRPC服务
-
修改RPC服务端代码
打开greeter_server.js,增加方法:(注意两个地方代码调整)
var PROTO_PATH = __dirname + '/../../protos/helloworld.proto'; var grpc = require('@grpc/grpc-js'); var protoLoader = require('@grpc/proto-loader'); var packageDefinition = protoLoader.loadSync( PROTO_PATH, {keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }); var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld; /** * Implements the SayHello RPC method. */ function sayHello(call, callback) { callback(null, {message: 'Hello ' + call.request.name}); } //@9ong 代码调整1 function sayHelloAgain(call, callback) { callback(null, {message: 'Hello again' + call.request.name}); } /** * Starts an RPC server that receives requests for the Greeter service at the * sample server port */ function main() { var server = new grpc.Server(); //@9ong 代码调整2 server.addService(hello_proto.Greeter.service, {sayHello: sayHello,sayHelloAgain: sayHelloAgain}); server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { server.start(); }); } main();
-
修改.proto服务定义文件
更新helloworld.proto服务定义:
// The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} rpc SayHelloAgain (HelloRequest) returns (HelloReply) {} }
-
重新生成RPC客户端stub classess
/** * The greeting service definition. */ class GreeterClient extends \Grpc\BaseStub { /** * @param string $hostname hostname * @param array $opts channel options * @param \Grpc\Channel $channel (optional) re-use channel object */ public function __construct($hostname, $opts, $channel = null) { parent::__construct($hostname, $opts, $channel); } /** * Sends a greeting * @param \Helloworld\HelloRequest $argument input argument * @param array $metadata metadata * @param array $options call options * @return \Grpc\UnaryCall */ public function SayHello(\Helloworld\HelloRequest $argument, $metadata = [], $options = []) { return $this->_simpleRequest('/helloworld.Greeter/SayHello', $argument, ['\Helloworld\HelloReply', 'decode'], $metadata, $options); } /** * @param \Helloworld\HelloRequest $argument input argument * @param array $metadata metadata * @param array $options call options * @return \Grpc\UnaryCall */ public function SayHelloAgain(\Helloworld\HelloRequest $argument, $metadata = [], $options = []) { return $this->_simpleRequest('/helloworld.Greeter/SayHelloAgain', $argument, ['\Helloworld\HelloReply', 'decode'], $metadata, $options); } }
-
测试执行
业务逻辑代码greeter_client.php:
function greet($hostname, $name) { $client = new Helloworld\GreeterClient($hostname, [ 'credentials' => Grpc\ChannelCredentials::createInsecure(), ]); $request = new Helloworld\HelloRequest(); $request->setName($name); list($response, $status) = $client->SayHello($request)->wait(); if ($status->code !== Grpc\STATUS_OK) { echo "ERROR: " . $status->code . ", " . $status->details . PHP_EOL; exit(1); } echo $response->getMessage() . PHP_EOL; list($reply, $status) = $client->SayHelloAgain($request)->wait(); echo $reply->getMessage().PHP_EOL; }
执行:
grpc/examples/php$ php greeter_client.php Hello world Hello againworld