HPROSE 是 High Performance Remote Object Service Engine 的缩写,翻译成中文就是“高性能远程对象服务引擎”。
它是一个先进的轻量级的跨语言跨平台面向对象的高性能远程动态通讯中间件。它不仅简单易用,而且功能强大。你只需要稍许的时间去学习,就能用它轻松构建跨语言跨平台的分布式应用系统了。
Hprose 支持众多流行的编程语言,例如:
通过 Hprose,你就可以在这些语言之间方便高效的实现互通了。
如果你正在使用 composer 管理你的项目,那么你不需要做任何特别处理。只要在 composer.json 中的 require 段添加了对 hprose/hprose 的引用就可以了。如果你需要 swoole 支持,添加 hprose/hprose-swoole 就可以了。
然后在代码这样引用:
<?php
require_once "vendor/autoload.php";
use Hprose\Swoole\Http\Server;
function hello($name) {
return "Hello $name!";
}
$server = new Server("http://0.0.0.0:8000");
$server->add("hello");
$server->debug = true;
$server->crossDomain = true;
$server->start();
手动管理方式
如果你不打算使用 composer 来管理你的项目,那你可以直接把 hprose-php 里面的 src 目录复制到你的项目中,然后改成任何你喜欢的名字,比如改为 hprose。
如果你还需要使用 hprose-swoole 下的文件,而且也不想使用 composer 来管理项目。你只需要把 hprose-swoole 下的 src 中的文件,复制到 hprose-php 下的 src 下对应的目录中,就可以了。
然后像这样引用它:
<?php
require_once 'hprose/Hprose.php';
use Hprose\Swoole\Http\Server;
function hello($name) {
return "Hello $name!";
}
$server = new Server("http://0.0.0.0:8000");
$server->add("hello");
$server->debug = true;
$server->crossDomain = true;
$server->start();
但是在后面其他章节中,为了方便统一,我们一律采用 composer 方式来写示例代码,而不再采用上面这种手动管理方式。
PHP 5.5 引入了 Generator
,Generator
通过封装之后,可以作为协程来进行使用。
Hprose 也提供了对 Generator
的一个封装,并且跟 Promise
相结合之后,可以实现异步代码同步化。
让我们来看一个例子:
use \Hprose\Future;
use \Hprose\Http\Client;
Future\co(function() {
$test = new Client("http://hprose.com/example/");
var_dump((yield $test->hello("hprose")));
$a = $test->sum(1, 2, 3);
$b = $test->sum(4, 5, 6);
$c = $test->sum(7, 8, 9);
var_dump((yield $test->sum($a, $b, $c)));
var_dump((yield $test->hello("world")));
});
Future\co
(也可以用 Promise\co
)就是一个协程封装函数。它的功能以协程的方式来执行生成器函数。该方法允许带入参数执行。
在上面的例子中,$test 是一个 Hprose 的异步 Http 客户端。Hprose 2.0 的默认客户端为异步客户端,而不是同步客户端,这一点与 1.x 不同。
所以 $test->hello
和 $test->sum
两个调用的返回值实际上是一个 promise
对象。而 yield
关键字在这里的作用就是,可以等待调用完成并返回 promise
所包含的值,如果 promise
的最后的状态为 REJECTED
,那么 yield
将抛出一个异常,异常的值为 promise
对象中的 reason
属性值。
在上面的调用中,$a
, $b
, $c
三个变量都是 promise
对象,而 $test->sum
可以直接接受 promise
参数作为调用参数,当 $a
, $b
, $c
三个 promise
对象的状态都变为 FULFILLED
状态时,$test-sum($a, $b, $c)
才会真正的开始调用。而获取 $a
,$b
,$c
的三个调用是异步并发执行的。
上面程序的执行结果为:
string(12) "Hello hprose"
int(45)
string(11) "Hello world"
从结果我们可以看出,co
函数和 yield
的结合可以很方便的让异步程序编写同步化。这也是 Hprose 2.0 最有特色的改进之一。
虽然上面用 Hprose 远程调用来举例,但是 co
函数所实现的协程不是只对 Hprose 远程调用有效,而是对任何返回 promise
的对象都有效。所以,即使你不使用 Hprose 远程调用,也可以使用 co
函数和 Promise 来进行异步代码的同步化编写。
我们前面说过,如果在同一个协程内进行远程调用,如果不加 yield 关键字,多个远程调用就是并发执行的。加上 yield 关键字,就会变成顺序执行。
那么当开两个或多个协程时,结果是什么样子呢?我们来看一个例子:
use \Hprose\Future;
use \Hprose\Http\Client;
$test = new Client("http://hprose.com/example/");
Future\co(function() use ($test) {
for ($i = 0; $i < 5; $i++) {
var_dump((yield $test->hello("1-" . $i)));
}
});
Future\co(function() use ($test) {
for ($i = 0; $i < 5; $i++) {
var_dump((yield $test->hello("2-" . $i)));
}
});
我们运行该程序之后,可以看到如下结果:
string(9) "Hello 2-0"
string(9) "Hello 1-0"
string(9) "Hello 1-1"
string(9) "Hello 2-1"
string(9) "Hello 2-2"
string(9) "Hello 1-2"
string(9) "Hello 1-3"
string(9) "Hello 2-3"
string(9) "Hello 2-4"
string(9) "Hello 1-4"
这个运行结果并不唯一,我们有可能看到不同顺序的输出,但是有一点可以保证,就是 Hello-1-X
中的 X
是按照顺序输出的,而 Hello-2-Y
中的 Y
也是按照顺序输出的。
也就是说,每个协程内的语句是按照顺序执行的,而两个协程确是并行执行的。
不过有一点要注意,上面的例子跟第一个例子有一点不同,那就是我们把 $test 客户端的创建拿到了协程外面。
如果像第一个例子那样放在协程中,我们就看不到这样的并发执行结果了。原因是,这里的每个客户端都有一个自己独立的事件循环,只有当一个事件循环执行完之后,才会执行另一个事件循环。
但如果换成 Hprose 的 Swoole 的客户端,则不管是放在里面还是外面,看到的都是并发执行的结果,原因是 Swoole 客户端的事件循环是统一的。而且 Swoole 的事件循环一旦开始,如果不手动执行 swoole_event_exit
,事件循环不会结束,你也就不会看到程序退出。在使用 Swoole 客户端时,需要注意这一点。
co
函数允许传参给协程。
而且 co
函数本身的返回值也是一个 promise
对象。它的值在 PHP 5 和 PHP 7 中略有差别。
PHP 5 的生成器函数本身不允许使用 return
返回值。例如:
function test() {
$x = (yield 1);
return $x;
}
这样的代码是非法的(但你不用担心因为写了这样的代码而被抓去坐牢)。但是在 PHP 7 中,这种写法是被允许的,但这个返回值跟普通函数的返回值是不同的。PHP 7 中为 Generator
对象提供了一个 getReturn
方法来专门获取这个返回值。
co
函数的返回值在 PHP 5 中是最后一次执行的 yield 的返回值的 promise
包装。在 PHP 7 中,如果没有 return
语句,或者 return
语句没有返回值(或者返回值为 NULL
),那么,co
函数的返回值跟 PHP 5 相同。如果在 PHP 7 中,使用了 return
语句并且有返回值,比如像上面那个 test
函数,那么返回值为 return
语句返回值的 promise
包装。
因此如果你的代码是按照 PHP 5 的方式编写的,那么执行的效果在 PHP 5 和 PHP 7 中是相同的,如果你是按照 PHP 7 的方式单独编写的,那么你也能够得到你希望得到的 PHP 7 的返回值。因此,co
函数既做到了对旧版本的兼容性,又做到了对新版本特殊功能的支持。
因为 co
函数的结果本身也是一个 promise
对象,因此,你也可以在另外一个协程中来 yield
co
函数的执行结果。
下面这个例子演示了传参和 co
函数返回值的使用:
use \Hprose\Future;
use \Hprose\Http\Client;
$test = new Client("http://hprose.com/example/");
function hello($n, $test) {
$result = array();
for ($i = 0; $i < 5; $i++) {
$result[] = $test->hello("$n-$i");
}
yield Future\all($result);
}
Future\co(function() use ($test) {
$result = (yield Future\co(function($test) {
$result = array();
for ($i = 0; $i < 3; $i++) {
$result[] = Future\co('hello', $i, $test);
}
yield Future\all($result);
}, $test));
var_dump($result);
});
有话要说