├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── README_zh_CN.md
├── composer.json
├── examples
├── composer.json
└── src
│ ├── HttpClient.php
│ ├── HttpClientBenchmark.php
│ ├── HttpServer.php
│ ├── PushClient.php
│ ├── PushServer.php
│ ├── TcpFDClient.php
│ ├── TcpFDClientBenchmark.php
│ ├── TcpHDClient.php
│ ├── TcpHDClientBenchmark.php
│ ├── TcpServer.php
│ ├── TimePushClient.php
│ ├── TimePushServer.php
│ ├── TimeoutClient.php
│ ├── TimeoutServer.php
│ ├── UnixClient.php
│ ├── UnixClientBenchmark.php
│ ├── UnixServer.php
│ ├── WebSocketClient.php
│ ├── WebSocketClientBenchmark.php
│ ├── WebSocketServer.php
│ └── promisify.php
├── install_ext.sh
├── src
└── Hprose
│ └── Swoole
│ ├── Client.php
│ ├── Http
│ ├── Client.php
│ ├── Server.php
│ ├── Service.php
│ └── Transporter.php
│ ├── Server.php
│ ├── Socket
│ ├── Client.php
│ ├── FullDuplexTransporter.php
│ ├── HalfDuplexTransporter.php
│ ├── Server.php
│ ├── Service.php
│ └── Transporter.php
│ ├── Timer.php
│ └── WebSocket
│ ├── Client.php
│ ├── Server.php
│ └── Service.php
└── tests
├── Bootstrap.php
└── phpunit.xml.dist
/.gitignore:
--------------------------------------------------------------------------------
1 | temp
2 | vendor
3 | composer.phar
4 | composer.lock
5 | netbeans
6 | nbproject
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - 5.4
4 | - 5.5
5 | - 5.6
6 | - 7.0
7 | - 7.1
8 | - 7.2
9 |
10 | sudo: required
11 |
12 | before_script:
13 | - chmod +x install_ext.sh
14 | - ./install_ext.sh
15 | - composer install
16 | - cd tests
17 |
18 | script: phpunit -v
19 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2008-2016 http://hprose.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 | # Hprose for Swoole
4 |
5 | [](https://travis-ci.org/hprose/hprose-swoole)
6 | 
7 | [](https://packagist.org/packages/hprose/hprose-swoole)
8 | [](https://packagist.org/packages/hprose/hprose-swoole)
9 | [](https://packagist.org/packages/hprose/hprose-swoole)
10 |
11 | ## Introduction
12 |
13 | *Hprose* is a High Performance Remote Object Service Engine.
14 |
15 | It is a modern, lightweight, cross-language, cross-platform, object-oriented, high performance, remote dynamic communication middleware. It is not only easy to use, but powerful. You just need a little time to learn, then you can use it to easily construct cross language cross platform distributed application system.
16 |
17 | *Hprose* supports many programming languages, for example:
18 |
19 | * AAuto Quicker
20 | * ActionScript
21 | * ASP
22 | * C++
23 | * Dart
24 | * Delphi/Free Pascal
25 | * dotNET(C#, Visual Basic...)
26 | * Golang
27 | * Java
28 | * JavaScript
29 | * Node.js
30 | * Objective-C
31 | * Perl
32 | * PHP
33 | * Python
34 | * Ruby
35 | * ...
36 |
37 | Through *Hprose*, You can conveniently and efficiently intercommunicate between those programming languages.
38 |
39 | This project is the implementation of Hprose for PHP based on swoole.
40 |
41 | More Documents for Hprose 2.0: https://github.com/hprose/hprose-php/wiki
42 |
43 | ## Installation
44 |
45 | ### Download Source Code
46 | [Download Link](https://github.com/hprose/hprose-swoole/archive/master.zip)
47 |
48 | ### install by `composer`
49 | ```javascript
50 | {
51 | "require": {
52 | "hprose/hprose-swoole": "dev-master"
53 | }
54 | }
55 | ```
56 |
57 | ## Usage
58 |
59 | You need to install [swoole](http://www.swoole.com/) first. The minimum version of [swoole](https://github.com/swoole/swoole-src) been supported is 1.8.8.
60 |
61 | You also need to install [hprose-pecl](https://pecl.php.net/package/hprose) 1.6.5+.
62 |
63 | ### Server
64 |
65 | Hprose for PHP is very easy to use.
66 |
67 | You can create a standalone hprose http server like this:
68 |
69 | `http_server.php`
70 | ```php
71 | addFunction('hello');
82 | $server->start();
83 | ```
84 |
85 | `tcp_server.php`
86 | ```php
87 | addFunction('hello');
98 | $server->start();
99 | ```
100 |
101 | `unix_server.php`
102 | ```php
103 | addFunction('hello');
114 | $server->start();
115 | ```
116 |
117 | `websocket_server.php`
118 | ```php
119 | addFunction('hello');
130 | $server->start();
131 | ```
132 |
133 | The websocket server is also a http server.
134 |
135 | ### Client
136 |
137 | Then you can create a hprose client to invoke it like this:
138 |
139 | `http_client.php`
140 | ```php
141 | hello('World')->then(function($result) {
148 | echo $result;
149 | }, function($e) {
150 | echo $e;
151 | });
152 | $client->hello('World 0', function() {
153 | echo "ok\r\n";
154 | });
155 | $client->hello('World 1', function($result) {
156 | echo $result . "\r\n";
157 | });
158 | $client->hello('World 2', function($result, $args) {
159 | echo $result . "\r\n";
160 | });
161 | $client->hello('World 3', function($result, $args, $error) {
162 | echo $result . "\r\n";
163 | });
164 | ```
165 |
166 | `tcp_client.php`
167 | ```php
168 | hello('World')->then(function($result) {
175 | echo $result;
176 | }, function($e) {
177 | echo $e;
178 | });
179 | $client->hello('World 0', function() {
180 | echo "ok\r\n";
181 | });
182 | $client->hello('World 1', function($result) {
183 | echo $result . "\r\n";
184 | });
185 | $client->hello('World 2', function($result, $args) {
186 | echo $result . "\r\n";
187 | });
188 | $client->hello('World 3', function($result, $args, $error) {
189 | echo $result . "\r\n";
190 | });
191 | ```
192 |
193 | The result of invoking is a promise object, you can also specify the callback function after the arguments, the callback function supports 0 - 3 parameters:
194 |
195 | |params |comments |
196 | |--------:|:------------------------------------------------------------------|
197 | |result |The result is the server returned, if no result, its value is null.|
198 | |arguments|It is an array of arguments. if no argument, it is an empty array. |
199 | |error |It is an object of Exception, if no error, its value is null. |
200 |
--------------------------------------------------------------------------------
/README_zh_CN.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Hprose for Swoole
4 |
5 | [](https://travis-ci.org/hprose/hprose-swoole)
6 | 
7 | [](https://packagist.org/packages/hprose/hprose-swoole)
8 | [](https://packagist.org/packages/hprose/hprose-swoole)
9 | [](https://packagist.org/packages/hprose/hprose-swoole)
10 |
11 | ## 简介
12 |
13 | *Hprose* 是高性能远程对象服务引擎(High Performance Remote Object Service Engine)的缩写。
14 |
15 | 它是一个先进的轻量级的跨语言跨平台面向对象的高性能远程动态通讯中间件。它不仅简单易用,而且功能强大。你只需要稍许的时间去学习,就能用它轻松构建跨语言跨平台的分布式应用系统了。
16 |
17 | *Hprose* 支持众多编程语言,例如:
18 |
19 | * AAuto Quicker
20 | * ActionScript
21 | * ASP
22 | * C++
23 | * Dart
24 | * Delphi/Free Pascal
25 | * dotNET(C#, Visual Basic...)
26 | * Golang
27 | * Java
28 | * JavaScript
29 | * Node.js
30 | * Objective-C
31 | * Perl
32 | * PHP
33 | * Python
34 | * Ruby
35 | * ...
36 |
37 | 通过 *Hprose*,你就可以在这些语言之间方便高效的实现互通了。
38 |
39 | 本项目是基于 swoole 扩展的 Hprose 的 PHP 语言版本实现。
40 |
41 | Hprose 2.0 更多文档: https://github.com/hprose/hprose-php/wiki
42 |
43 | ## 安装
44 |
45 | ### 通过下载源码
46 | [下载地址](https://github.com/hprose/hprose-swoole/archive/master.zip)
47 |
48 | ### 通过 composer
49 | ```javascript
50 | {
51 | "require": {
52 | "hprose/hprose-swoole": "dev-master"
53 | }
54 | }
55 | ```
56 |
57 | ## 使用
58 |
59 | 你首先需要安装 [swoole](http://www.swoole.com/)。[swoole](https://github.com/swoole/swoole-src) 被支持的最低版本为 1.8.8.
60 |
61 | ### 服务器
62 |
63 | Hprose for PHP 使用起来很简单,例如:
64 |
65 | `http_server.php`
66 | ```php
67 | addFunction('hello');
78 | $server->start();
79 | ```
80 |
81 | `tcp_server.php`
82 | ```php
83 | addFunction('hello');
94 | $server->start();
95 | ```
96 |
97 | `unix_server.php`
98 | ```php
99 | addFunction('hello');
110 | $server->start();
111 | ```
112 |
113 | `websocket_server.php`
114 | ```php
115 | addFunction('hello');
126 | $server->start();
127 | ```
128 |
129 | WebSocket 服务器同时也是 HTTP 服务器,所以既可以用 WebSocket 客户端访问,也可以用 HTTP 客户端访问。
130 |
131 | ### Client
132 |
133 | 然后你可以创建一个 Hprose 的客户端来调用它了,就像这样:
134 |
135 | `http_client.php`
136 | ```php
137 | hello('World')->then(function($result) {
144 | echo $result;
145 | }, function($e) {
146 | echo $e;
147 | });
148 | $client->hello('World 0', function() {
149 | echo "ok\r\n";
150 | });
151 | $client->hello('World 1', function($result) {
152 | echo $result . "\r\n";
153 | });
154 | $client->hello('World 2', function($result, $args) {
155 | echo $result . "\r\n";
156 | });
157 | $client->hello('World 3', function($result, $args, $error) {
158 | echo $result . "\r\n";
159 | });
160 | ```
161 |
162 | `tcp_client.php`
163 | ```php
164 | hello('World')->then(function($result) {
171 | echo $result;
172 | }, function($e) {
173 | echo $e;
174 | });
175 | $client->hello('World 0', function() {
176 | echo "ok\r\n";
177 | });
178 | $client->hello('World 1', function($result) {
179 | echo $result . "\r\n";
180 | });
181 | $client->hello('World 2', function($result, $args) {
182 | echo $result . "\r\n";
183 | });
184 | $client->hello('World 3', function($result, $args, $error) {
185 | echo $result . "\r\n";
186 | });
187 | ```
188 |
189 | 直接调用的结果是一个 Promise 对象,也可以在调用时直接指定回调函数,回调函数支持 0 - 3 个参数。它们分别表示:
190 |
191 | |参数 |解释 |
192 | |-------:|:---------------------------------------------------------|
193 | |结果 |就是服务器端的返回结果,如果没有结果则为 null。 |
194 | |调用参数|是一个包含了调用参数的数组,如果调用没有参数,则为 0 个元素的数组。 |
195 | |错误 |一个 Exception 对象,如果没有错误则为 null。 |
196 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hprose/hprose-swoole",
3 | "type": "library",
4 | "description": "Hprose asynchronous client & standalone server based on swoole",
5 | "keywords": [
6 | "hprose",
7 | "phprpc",
8 | "rpc",
9 | "webservice",
10 | "websocket",
11 | "http",
12 | "ajax",
13 | "json",
14 | "jsonrpc",
15 | "xmlrpc",
16 | "cross-language",
17 | "cross-platform",
18 | "cross-domain",
19 | "html5",
20 | "serialize",
21 | "serialization",
22 | "protocol",
23 | "web",
24 | "service",
25 | "framework",
26 | "library",
27 | "game",
28 | "communication",
29 | "middleware",
30 | "webapi",
31 | "socket",
32 | "tcp",
33 | "async",
34 | "unix",
35 | "future"
36 | ],
37 | "homepage": "http://hprose.com/",
38 | "license": "MIT",
39 | "authors": [
40 | {
41 | "name": "Ma Bingyao",
42 | "email": "andot@hprose.com",
43 | "homepage": "http://hprose.com",
44 | "role": "Developer"
45 | }
46 | ],
47 | "require": {
48 | "php": ">=5.3.0",
49 | "hprose/hprose": ">=2.0.23",
50 | "ext-swoole": ">=1.8.8",
51 | "ext-hprose": ">=1.6.5"
52 | },
53 | "require-dev": {
54 | "phpunit/phpunit": ">=4.0.0"
55 | },
56 | "autoload": {
57 | "psr-4": {
58 | "Hprose\\Swoole\\": "src/Hprose/Swoole"
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/examples/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hprose/hprose-swoole-examples",
3 | "description": "examples of hprose-swoole",
4 | "authors": [
5 | {
6 | "name": "andot",
7 | "email": "mabingyao@gmail.com"
8 | }
9 | ],
10 | "require": {
11 | "php": ">=5.3.0",
12 | "hprose/hprose": "dev-master",
13 | "hprose/hprose-swoole": "dev-master"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/src/HttpClient.php:
--------------------------------------------------------------------------------
1 | hello("yield world1")));
13 | var_dump((yield $test->hello("yield world2")));
14 | var_dump((yield $test->hello("yield world3")));
15 | var_dump((yield $test->hello("yield world4")));
16 | var_dump((yield $test->hello("yield world5")));
17 | var_dump((yield $test->hello("yield world6")));
18 | }
19 | catch (\Exception $e) {
20 | echo ($e);
21 | }
22 | });
23 |
24 | $var_dump($test->hello("async world1"));
25 | $var_dump($test->hello("async world2"));
26 | $var_dump($test->hello("async world3"));
27 | $var_dump($test->hello("async world4"));
28 | $var_dump($test->hello("async world5"));
29 | $var_dump($test->hello("async world6"));
30 |
31 | $test->hello("world1")
32 | ->then(function($result) use ($test) {
33 | var_dump($result);
34 | return $test->hello("world2");
35 | })
36 | ->then(function($result) use ($test) {
37 | var_dump($result);
38 | return $test->hello("world3");
39 | })
40 | ->then(function($result) use ($test) {
41 | var_dump($result);
42 | return $test->hello("world4");
43 | })
44 | ->then(function($result) use ($test) {
45 | var_dump($result);
46 | return $test->hello("world5");
47 | })
48 | ->then(function($result) use ($test) {
49 | var_dump($result);
50 | return $test->hello("world6");
51 | })
52 | ->then(function($result) {
53 | var_dump($result);
54 | });
55 |
--------------------------------------------------------------------------------
/examples/src/HttpClientBenchmark.php:
--------------------------------------------------------------------------------
1 | hello("$j-$i"));
16 | }
17 | (yield Future\all($results));
18 | }
19 | $total = microtime(true) - $start;
20 | $average = $total / $n / $m;
21 | var_dump($total);
22 | var_dump($average);
23 | });
24 |
--------------------------------------------------------------------------------
/examples/src/HttpServer.php:
--------------------------------------------------------------------------------
1 | setErrorTypes(E_ALL);
11 | $server->setDebugEnabled();
12 | $server->setCrossDomainEnabled();
13 | $server->addFunction('hello');
14 | $server->start();
15 |
--------------------------------------------------------------------------------
/examples/src/PushClient.php:
--------------------------------------------------------------------------------
1 | getId());
10 | $client->subscribe('news', $id, function($news) {
11 | var_dump($news);
12 | });
13 | var_dump((yield $client->hello('hprose')));
14 | });
15 |
--------------------------------------------------------------------------------
/examples/src/PushServer.php:
--------------------------------------------------------------------------------
1 | clients->push("news", "this is a pushed message: $name");
8 | $context->clients->broadcast("news", array('x' => 1, 'y' => 2));
9 | $fdinfo = $context->server->connection_info($context->socket);
10 | return "Hello $name! -- {$fdinfo['remote_ip']}";
11 | }
12 |
13 | $server = new Server("tcp://0.0.0.0:1980");
14 | $server->publish('news');
15 | $server->onSubscribe = function($topic, $id, $service) {
16 | error_log("client $id subscribe $topic on " . microtime(true));
17 | };
18 | $server->onUnsubscribe = function($topic, $id, $service) {
19 | error_log("client $id unsubscribe $topic on " . microtime(true));
20 | };
21 | $server->addFunction('hello', array('passContext' => true));
22 | $server->start();
23 |
--------------------------------------------------------------------------------
/examples/src/TcpFDClient.php:
--------------------------------------------------------------------------------
1 | fullDuplex = true;
9 | $var_dump = Future\wrap("var_dump");
10 |
11 | Future\co(function() use ($test) {
12 | try {
13 | var_dump((yield $test->hello("yield world1")));
14 | var_dump((yield $test->hello("yield world2")));
15 | var_dump((yield $test->hello("yield world3")));
16 | var_dump((yield $test->hello("yield world4")));
17 | var_dump((yield $test->hello("yield world5")));
18 | var_dump((yield $test->hello("yield world6")));
19 | }
20 | catch (\Exception $e) {
21 | echo ($e);
22 | }
23 | });
24 |
25 | $var_dump($test->hello("async world1"));
26 | $var_dump($test->hello("async world2"));
27 | $var_dump($test->hello("async world3"));
28 | $var_dump($test->hello("async world4"));
29 | $var_dump($test->hello("async world5"));
30 | $var_dump($test->hello("async world6"));
31 |
32 | $test->hello("world1")
33 | ->then(function($result) use ($test) {
34 | var_dump($result);
35 | return $test->hello("world2");
36 | })
37 | ->then(function($result) use ($test) {
38 | var_dump($result);
39 | return $test->hello("world3");
40 | })
41 | ->then(function($result) use ($test) {
42 | var_dump($result);
43 | return $test->hello("world4");
44 | })
45 | ->then(function($result) use ($test) {
46 | var_dump($result);
47 | return $test->hello("world5");
48 | })
49 | ->then(function($result) use ($test) {
50 | var_dump($result);
51 | return $test->hello("world6");
52 | })
53 | ->then(function($result) {
54 | var_dump($result);
55 | });
56 |
--------------------------------------------------------------------------------
/examples/src/TcpFDClientBenchmark.php:
--------------------------------------------------------------------------------
1 | fullDuplex = true;
10 | $start = microtime(true);
11 | $n = 100;
12 | $m = 100;
13 | for ($j = 0; $j < $n; $j++) {
14 | $results = array();
15 | for ($i = 0; $i < $m; $i++) {
16 | $results[] = ($test->hello("$j-$i"));
17 | }
18 | (yield Future\all($results));
19 | }
20 | $total = microtime(true) - $start;
21 | $average = $total / $n / $m;
22 | var_dump($total);
23 | var_dump($average);
24 | });
25 |
--------------------------------------------------------------------------------
/examples/src/TcpHDClient.php:
--------------------------------------------------------------------------------
1 | fullDuplex = false;
9 | $var_dump = Future\wrap("var_dump");
10 |
11 | Future\co(function() use ($test) {
12 | try {
13 | var_dump((yield $test->hello("yield world1")));
14 | var_dump((yield $test->hello("yield world2")));
15 | var_dump((yield $test->hello("yield world3")));
16 | var_dump((yield $test->hello("yield world4")));
17 | var_dump((yield $test->hello("yield world5")));
18 | var_dump((yield $test->hello("yield world6")));
19 | }
20 | catch (\Exception $e) {
21 | echo ($e);
22 | }
23 | });
24 |
25 | $var_dump($test->hello("async world1"));
26 | $var_dump($test->hello("async world2"));
27 | $var_dump($test->hello("async world3"));
28 | $var_dump($test->hello("async world4"));
29 | $var_dump($test->hello("async world5"));
30 | $var_dump($test->hello("async world6"));
31 |
32 | $test->hello("world1")
33 | ->then(function($result) use ($test) {
34 | var_dump($result);
35 | return $test->hello("world2");
36 | })
37 | ->then(function($result) use ($test) {
38 | var_dump($result);
39 | return $test->hello("world3");
40 | })
41 | ->then(function($result) use ($test) {
42 | var_dump($result);
43 | return $test->hello("world4");
44 | })
45 | ->then(function($result) use ($test) {
46 | var_dump($result);
47 | return $test->hello("world5");
48 | })
49 | ->then(function($result) use ($test) {
50 | var_dump($result);
51 | return $test->hello("world6");
52 | })
53 | ->then(function($result) {
54 | var_dump($result);
55 | });
56 |
--------------------------------------------------------------------------------
/examples/src/TcpHDClientBenchmark.php:
--------------------------------------------------------------------------------
1 | fullDuplex = false;
10 | $start = microtime(true);
11 | $n = 100;
12 | $m = 100;
13 | for ($j = 0; $j < $n; $j++) {
14 | $results = array();
15 | for ($i = 0; $i < $m; $i++) {
16 | $results[] = ($test->hello("$j-$i"));
17 | }
18 | (yield Future\all($results));
19 | }
20 | $total = microtime(true) - $start;
21 | $average = $total / $n / $m;
22 | var_dump($total);
23 | var_dump($average);
24 | });
25 |
--------------------------------------------------------------------------------
/examples/src/TcpServer.php:
--------------------------------------------------------------------------------
1 | setErrorTypes(E_ALL);
11 | $server->setDebugEnabled();
12 | $server->addFunction('hello');
13 | $server->start();
14 |
--------------------------------------------------------------------------------
/examples/src/TimePushClient.php:
--------------------------------------------------------------------------------
1 | subscribe('time', function($date) use ($client, &$count) {
9 | if (++$count > 10) {
10 | $client->unsubscribe('time');
11 | swoole_event_exit();
12 | }
13 | else {
14 | var_dump($date);
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/examples/src/TimePushServer.php:
--------------------------------------------------------------------------------
1 | publish('time');
8 | $server->on('workerStart', function($serv) use ($server) {
9 | $serv->tick(1000, function() use ($server) {
10 | $server->push('time', microtime(true));
11 | });
12 | });
13 | $server->start();
14 |
--------------------------------------------------------------------------------
/examples/src/TimeoutClient.php:
--------------------------------------------------------------------------------
1 | fullDuplex = true;
9 | $test->timeout = 600;
10 |
11 | $test->sum(1, 2)->catchError(function($e) {
12 | //echo $e;
13 | });
14 | $test->sum(1, 2)->then(function($result) {
15 | echo "1 + 2 = " . $result;
16 | })->catchError(function($e) use ($test) {
17 | //echo $e;
18 | $test->sum(2, 3, new InvokeSettings(array('timeout' => 20000)))
19 | ->then(function($result) {
20 | echo "2 + 3 = " . $result;
21 | })->catchError(function($e) {
22 | echo $e;
23 | });
24 | });
--------------------------------------------------------------------------------
/examples/src/TimeoutServer.php:
--------------------------------------------------------------------------------
1 | setErrorTypes(E_ALL);
9 | $server->setDebugEnabled();
10 | $server->addFunction(function($a, $b) use ($server) {
11 | $promise = new Future();
12 | swoole_timer_after(1000, function() use($a, $b, $promise) {
13 | $promise->resolve($a + $b);
14 | });
15 | return $promise;
16 | }, "sum");
17 | $server->start();
18 |
--------------------------------------------------------------------------------
/examples/src/UnixClient.php:
--------------------------------------------------------------------------------
1 | hello("yield world1")));
13 | var_dump((yield $test->hello("yield world2")));
14 | var_dump((yield $test->hello("yield world3")));
15 | var_dump((yield $test->hello("yield world4")));
16 | var_dump((yield $test->hello("yield world5")));
17 | var_dump((yield $test->hello("yield world6")));
18 | }
19 | catch (\Exception $e) {
20 | echo ($e);
21 | }
22 | });
23 |
24 | $var_dump($test->hello("async world1"));
25 | $var_dump($test->hello("async world2"));
26 | $var_dump($test->hello("async world3"));
27 | $var_dump($test->hello("async world4"));
28 | $var_dump($test->hello("async world5"));
29 | $var_dump($test->hello("async world6"));
30 |
31 | $test->hello("world1")
32 | ->then(function($result) use ($test) {
33 | var_dump($result);
34 | return $test->hello("world2");
35 | })
36 | ->then(function($result) use ($test) {
37 | var_dump($result);
38 | return $test->hello("world3");
39 | })
40 | ->then(function($result) use ($test) {
41 | var_dump($result);
42 | return $test->hello("world4");
43 | })
44 | ->then(function($result) use ($test) {
45 | var_dump($result);
46 | return $test->hello("world5");
47 | })
48 | ->then(function($result) use ($test) {
49 | var_dump($result);
50 | return $test->hello("world6");
51 | })
52 | ->then(function($result) {
53 | var_dump($result);
54 | });
55 |
--------------------------------------------------------------------------------
/examples/src/UnixClientBenchmark.php:
--------------------------------------------------------------------------------
1 | hello("$j-$i"));
16 | }
17 | (yield Future\all($results));
18 | }
19 | $total = microtime(true) - $start;
20 | $average = $total / $n / $m;
21 | var_dump($total);
22 | var_dump($average);
23 | });
24 |
--------------------------------------------------------------------------------
/examples/src/UnixServer.php:
--------------------------------------------------------------------------------
1 | setErrorTypes(E_ALL);
11 | $server->setDebugEnabled();
12 | $server->addFunction('hello');
13 | $server->start();
14 |
--------------------------------------------------------------------------------
/examples/src/WebSocketClient.php:
--------------------------------------------------------------------------------
1 | hello("yield world1")));
13 | var_dump((yield $test->hello("yield world2")));
14 | var_dump((yield $test->hello("yield world3")));
15 | var_dump((yield $test->hello("yield world4")));
16 | var_dump((yield $test->hello("yield world5")));
17 | var_dump((yield $test->hello("yield world6")));
18 | }
19 | catch (\Exception $e) {
20 | echo ($e);
21 | }
22 | });
23 |
24 | $var_dump($test->hello("async world1"));
25 | $var_dump($test->hello("async world2"));
26 | $var_dump($test->hello("async world3"));
27 | $var_dump($test->hello("async world4"));
28 | $var_dump($test->hello("async world5"));
29 | $var_dump($test->hello("async world6"));
30 |
31 | $test->hello("world1")
32 | ->then(function($result) use ($test) {
33 | var_dump($result);
34 | return $test->hello("world2");
35 | })
36 | ->then(function($result) use ($test) {
37 | var_dump($result);
38 | return $test->hello("world3");
39 | })
40 | ->then(function($result) use ($test) {
41 | var_dump($result);
42 | return $test->hello("world4");
43 | })
44 | ->then(function($result) use ($test) {
45 | var_dump($result);
46 | return $test->hello("world5");
47 | })
48 | ->then(function($result) use ($test) {
49 | var_dump($result);
50 | return $test->hello("world6");
51 | })
52 | ->then(function($result) {
53 | var_dump($result);
54 | });
55 |
--------------------------------------------------------------------------------
/examples/src/WebSocketClientBenchmark.php:
--------------------------------------------------------------------------------
1 | hello("$j-$i"));
16 | }
17 | (yield Future\all($results));
18 | }
19 | $total = microtime(true) - $start;
20 | $average = $total / $n / $m;
21 | var_dump($total);
22 | var_dump($average);
23 | });
24 |
--------------------------------------------------------------------------------
/examples/src/WebSocketServer.php:
--------------------------------------------------------------------------------
1 | setErrorTypes(E_ALL);
11 | $server->setDebugEnabled();
12 | $server->addFunction('hello');
13 | $server->start();
14 |
--------------------------------------------------------------------------------
/examples/src/promisify.php:
--------------------------------------------------------------------------------
1 | body);
11 | });
--------------------------------------------------------------------------------
/install_ext.sh:
--------------------------------------------------------------------------------
1 | wget https://pecl.php.net/get/swoole-1.8.8.tgz
2 | tar zxvfp swoole-1.8.8.tgz
3 | cd swoole-1.8.8
4 | phpize
5 | ./configure
6 | make
7 | make install
8 | echo "extension = swoole.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
9 | wget https://pecl.php.net/get/hprose-1.6.5.tgz
10 | tar zxvfp hprose-1.6.5.tgz
11 | cd hprose-1.6.5
12 | phpize
13 | ./configure
14 | make
15 | make install
16 | echo "extension = hprose.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
17 | cd ..
18 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Client.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole;
23 |
24 | use Exception;
25 |
26 | class Client {
27 | private static $clientFactories = array();
28 | private static $clientFactoriesInited = false;
29 | public static function registerClientFactory($scheme, $clientFactory) {
30 | self::$clientFactories[$scheme] = $clientFactory;
31 | }
32 | public static function tryRegisterClientFactory($scheme, $clientFactory) {
33 | if (empty(self::$clientFactories[$scheme])) {
34 | self::$clientFactories[$scheme] = $clientFactory;
35 | }
36 | }
37 | private static function initClientFactories() {
38 | self::tryRegisterClientFactory("http", "\\Hprose\\Swoole\\Http\\Client");
39 | self::tryRegisterClientFactory("https", "\\Hprose\\Swoole\\Http\\Client");
40 | self::tryRegisterClientFactory("tcp", "\\Hprose\\Swoole\\Socket\\Client");
41 | self::tryRegisterClientFactory("tcp4", "\\Hprose\\Swoole\\Socket\\Client");
42 | self::tryRegisterClientFactory("tcp6", "\\Hprose\\Swoole\\Socket\\Client");
43 | self::tryRegisterClientFactory("ssl", "\\Hprose\\Swoole\\Socket\\Client");
44 | self::tryRegisterClientFactory("sslv2", "\\Hprose\\Swoole\\Socket\\Client");
45 | self::tryRegisterClientFactory("sslv3", "\\Hprose\\Swoole\\Socket\\Client");
46 | self::tryRegisterClientFactory("tls", "\\Hprose\\Swoole\\Socket\\Client");
47 | self::tryRegisterClientFactory("unix", "\\Hprose\\Swoole\\Socket\\Client");
48 | self::tryRegisterClientFactory("ws", "\\Hprose\\Swoole\\WebSocket\\Client");
49 | self::tryRegisterClientFactory("wss", "\\Hprose\\Swoole\\WebSocket\\Client");
50 | self::$clientFactoriesInited = true;
51 | }
52 | public static function create($uris) {
53 | if (!self::$clientFactoriesInited) self::initClientFactories();
54 | if (is_string($uris)) $uris = array($uris);
55 | $scheme = strtolower(parse_url($uris[0], PHP_URL_SCHEME));
56 | $n = count($uris);
57 | for ($i = 1; $i < $n; ++$i) {
58 | if (strtolower(parse_url($uris[$i], PHP_URL_SCHEME)) != $scheme) {
59 | throw new Exception("Not support multiple protocol.");
60 | }
61 | }
62 | $clientFactory = self::$clientFactories[$scheme];
63 | if (empty($clientFactory)) {
64 | throw new Exception("This client doesn't support $scheme scheme.");
65 | }
66 | return new $clientFactory($uris);
67 | }
68 | private $realClient = null;
69 | public function __construct($uris) {
70 | $this->realClient = self::create($uris);
71 | }
72 | public function __call($name, $args) {
73 | return call_user_func_array(array($this->realClient, $name), $args);
74 | }
75 | public function __set($name, $value) {
76 | $this->realClient->$name = $value;
77 | }
78 | public function __get($name) {
79 | return $this->realClient->$name;
80 | }
81 | public function __isset($name) {
82 | return isset($this->realClient->$name);
83 | }
84 | public function __unset($name) {
85 | unset($this->realClient->$name);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Http/Client.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\Http;
23 |
24 | use stdClass;
25 | use Exception;
26 | use Hprose\Future;
27 | use swoole_http_client;
28 |
29 | class Client extends \Hprose\Client {
30 | public $type;
31 | public $host = '';
32 | public $ip = '';
33 | public $port = 80;
34 | public $ssl = false;
35 | public $keepAlive = true;
36 | public $keepAliveTimeout = 300;
37 | public $poolTimeout = 30000;
38 | public $maxPoolSize = 10;
39 | public $header = array();
40 | private $trans;
41 | public function __construct($uris = null) {
42 | parent::__construct($uris);
43 | $this->trans = new Transporter($this);
44 | }
45 | public function setHeader($name, $value) {
46 | $lname = strtolower($name);
47 | if ($lname != 'content-type' &&
48 | $lname != 'content-length' &&
49 | $lname != 'host') {
50 | if ($value) {
51 | $this->header[$name] = $value;
52 | }
53 | else {
54 | unset($this->header[$name]);
55 | }
56 | }
57 | }
58 | public function setKeepAlive($keepAlive = true) {
59 | $this->keepAlive = $keepAlive;
60 | $this->header['Connection'] = $keepAlive ? 'keep-alive' : 'close';
61 | if ($keepAlive) {
62 | $this->header['Keep-Ailve'] = $this->keepAliveTimeout;
63 | }
64 | else {
65 | unset($this->header['Keep-Ailve']);
66 | }
67 | }
68 | public function isKeepAlive() {
69 | return $this->keepAlive;
70 | }
71 | public function setKeepAliveTimeout($timeout) {
72 | $this->keepAliveTimeout = $timeout;
73 | if ($this->keepAlive) {
74 | $this->header['Keep-Ailve'] = $timeout;
75 | }
76 | }
77 | public function getKeepAliveTimeout() {
78 | return $this->keepAliveTimeout;
79 | }
80 | public function setMaxPoolSize($value) {
81 | $this->maxPoolSize = $value;
82 | }
83 | public function getMaxPoolSize() {
84 | return $this->maxPoolSize;
85 | }
86 | public function setPoolTimeout($value) {
87 | $this->poolTimeout = $value;
88 | }
89 | public function getPoolTimeout() {
90 | return $this->poolTimeout;
91 | }
92 | public function getHost() {
93 | return $this->host;
94 | }
95 | public function getPort() {
96 | return $this->port;
97 | }
98 | public function isSSL() {
99 | return $this->ssl;
100 | }
101 | protected function setUri($uri) {
102 | parent::setUri($uri);
103 | $p = parse_url($uri);
104 | if ($p) {
105 | switch (strtolower($p['scheme'])) {
106 | case 'http':
107 | $this->host = $p['host'];
108 | $this->port = isset($p['port']) ? $p['port'] : 80;
109 | $this->path = isset($p['path']) ? $p['path'] : '/';
110 | $this->ssl = false;
111 | break;
112 | case 'https':
113 | $this->host = $p['host'];
114 | $this->port = isset($p['port']) ? $p['port'] : 443;
115 | $this->path = isset($p['path']) ? $p['path'] : '/';
116 | $this->ssl = true;
117 | break;
118 | default:
119 | throw new Exception("Only support http and https scheme");
120 | }
121 | }
122 | else {
123 | throw new Exception("Can't parse this uri: " . $uri);
124 | }
125 | $this->header['Host'] = $this->host;
126 | $this->header['Connection'] = $this->keepAlive ? 'keep-alive' : 'close';
127 | if ($this->keepAlive) {
128 | $this->header['Keep-Ailve'] = $this->keepAliveTimeout;
129 | }
130 | if (filter_var($this->host, FILTER_VALIDATE_IP) === false) {
131 | $ip = gethostbyname($this->host);
132 | if ($ip === $this->host) {
133 | throw new Exception('DNS lookup failed');
134 | }
135 | else {
136 | $this->ip = $ip;
137 | }
138 | }
139 | else {
140 | $this->ip = $this->host;
141 | }
142 | }
143 | protected function wait($interval, $callback) {
144 | $future = new Future();
145 | swoole_timer_after($interval * 1000, function() use ($future, $callback) {
146 | Future\sync($callback)->fill($future);
147 | });
148 | return $future;
149 | }
150 | protected function sendAndReceive($request, stdClass $context) {
151 | $future = new Future();
152 | $this->trans->sendAndReceive($request, $future, $context);
153 | if ($context->oneway) {
154 | $future->resolve(null);
155 | }
156 | return $future;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Http/Server.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\Http;
23 |
24 | use stdClass;
25 | use Exception;
26 | use swoole_http_server;
27 |
28 | class Server extends Service {
29 | public $server;
30 | public $settings = array();
31 | private function parseUrl($uri) {
32 | $result = new stdClass();
33 | $p = parse_url($uri);
34 | if ($p) {
35 | switch (strtolower($p['scheme'])) {
36 | case 'http':
37 | $result->host = $p['host'];
38 | $result->port = isset($p['port']) ? $p['port'] : 80;
39 | $result->type = SWOOLE_SOCK_TCP;
40 | break;
41 | case 'https':
42 | $result->host = $p['host'];
43 | $result->port = isset($p['port']) ? $p['port'] : 443;
44 | $result->type = SWOOLE_SOCK_TCP | SWOOLE_SSL;
45 | break;
46 | default:
47 | throw new Exception("Can't support this scheme: {$p['scheme']}");
48 | }
49 | }
50 | else {
51 | throw new Exception("Can't parse this uri: $uri");
52 | }
53 | return $result;
54 | }
55 | public function __construct($uri, $mode = SWOOLE_BASE) {
56 | parent::__construct();
57 | $url = $this->parseUrl($uri);
58 | $this->server = new swoole_http_server($url->host, $url->port, $mode, $url->type);
59 | }
60 | public function set($settings) {
61 | $this->settings = array_replace($this->settings, $settings);
62 | }
63 | public function on($name, $callback) {
64 | $this->server->on($name, $callback);
65 | }
66 | public function addListener($uri) {
67 | $url = $this->parseUrl($uri);
68 | $this->server->addListener($url->host, $url->port);
69 | }
70 | public function listen($host, $port, $type) {
71 | return $this->server->listen($host, $port, $type);
72 | }
73 | public function start() {
74 | if (is_array($this->settings) && !empty($this->settings)) {
75 | $this->server->set($this->settings);
76 | }
77 | $this->httpHandle($this->server);
78 | $this->server->start();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Http/Service.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\Http;
23 |
24 | use Hprose\Swoole\Timer;
25 |
26 | class Service extends \Hprose\Http\Service {
27 | const ORIGIN = 'origin';
28 | const MAX_PACK_LEN = 0x200000;
29 | private $crossDomainXmlFile = null;
30 | private $crossDomainXmlContent = null;
31 | private $clientAccessPolicyXmlFile = null;
32 | private $clientAccessPolicyXmlContent = null;
33 | private $lastModified;
34 | private $etag;
35 | public function __construct() {
36 | parent::__construct();
37 | $this->lastModified = gmstrftime("%a, %d %b %Y %T %Z", time());
38 | $this->etag = '"' . dechex(mt_rand()) . ':' . dechex(mt_rand()) . '"';
39 | $this->timer = new Timer();
40 | }
41 | public function header($name, $value, $context) {
42 | $context->response->header($name, $value);
43 | }
44 | public function getAttribute($name, $context) {
45 | return $context->request->header[$name];
46 | }
47 | public function hasAttribute($name, $context) {
48 | return array_key_exists($name, $context->request->header);
49 | }
50 | protected function readRequest($context) {
51 | return $context->request->rawContent();
52 | }
53 | public function writeResponse($data, $context) {
54 | $response = $context->response;
55 | $len = strlen($data);
56 | if ($len <= self::MAX_PACK_LEN) {
57 | $response->end($data);
58 | }
59 | else {
60 | for ($i = 0; $i < $len; $i += self::MAX_PACK_LEN) {
61 | if (!$response->write(substr($data, $i, min($len - $i, self::MAX_PACK_LEN)))) {
62 | return false;
63 | }
64 | }
65 | $response->end();
66 | }
67 | return true;
68 | }
69 | public function isGet($context) {
70 | return $context->request->server['request_method'] == 'GET';
71 | }
72 | public function isPost($context) {
73 | return $context->request->server['request_method'] == 'POST';
74 | }
75 | public function getCrossDomainXmlFile() {
76 | return $this->crossDomainXmlFile;
77 | }
78 | public function setCrossDomainXmlFile($value) {
79 | $this->crossDomainXmlFile = $value;
80 | $this->crossDomainXmlContent = file_get_contents($value);
81 | }
82 | public function getCrossDomainXmlContent() {
83 | return $this->crossDomainXmlContent;
84 | }
85 | public function setCrossDomainXmlContent($value) {
86 | $this->crossDomainXmlFile = null;
87 | $this->crossDomainXmlContent = $value;
88 | }
89 | public function getClientAccessPolicyXmlFile() {
90 | return $this->clientAccessPolicyXmlFile;
91 | }
92 | public function setClientAccessPolicyXmlFile($value) {
93 | $this->clientAccessPolicyXmlFile = $value;
94 | $this->clientAccessPolicyXmlContent = file_get_contents($value);
95 | }
96 | public function getClientAccessPolicyXmlContent() {
97 | return $this->clientAccessPolicyXmlContent;
98 | }
99 | public function setClientAccessPolicyXmlContent($value) {
100 | $this->clientAccessPolicyXmlFile = null;
101 | $this->clientAccessPolicyXmlContent = $value;
102 | }
103 | private function crossDomainXmlHandler($request, $response) {
104 | if ($request->server['path_info'] === '/crossdomain.xml') {
105 | if ($request->header['if-modified-since'] === $this->lastModified &&
106 | $request->headers['if-none-match'] === $this->etag) {
107 | $response->status(304);
108 | }
109 | else {
110 | $response->header('Last-Modified', $this->lastModified);
111 | $response->header('Etag', $this->etag);
112 | $response->header('Content-Type', 'text/xml');
113 | $response->header('Content-Length', strlen($this->crossDomainXmlContent));
114 | $response->write($this->crossDomainXmlContent);
115 | }
116 | $response->end();
117 | return true;
118 | }
119 | return false;
120 | }
121 | private function clientAccessPolicyXmlHandler($request, $response) {
122 | if ($request->server['path_info'] === '/clientaccesspolicy.xml') {
123 | if ($request->header['if-modified-since'] === $this->lastModified &&
124 | $request->headers['if-none-match'] === $this->etag) {
125 | $response->status(304);
126 | }
127 | else {
128 | $response->header('Last-Modified', $this->lastModified);
129 | $response->header('Etag', $this->etag);
130 | $response->header('Content-Type', 'text/xml');
131 | $response->header('Content-Length', strlen($this->clientAccessPolicyXmlContent));
132 | $response->write($this->clientAccessPolicyXmlContent);
133 | }
134 | $response->end();
135 | return true;
136 | }
137 | return false;
138 | }
139 | public function handle($request = null, $response = null) {
140 | if ($this->clientAccessPolicyXmlContent !== null &&
141 | $this->clientAccessPolicyXmlHandler($request, $response)) {
142 | return $response;
143 | }
144 | if ($this->crossDomainXmlContent !== null &&
145 | $this->crossDomainXmlHandler($request, $response)) {
146 | return $response;
147 | }
148 | return parent::handle($request, $response);
149 | }
150 | public function httpHandle($server) {
151 | $server->on('request', array($this, 'handle'));
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Http/Transporter.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\Http;
23 |
24 | use stdClass;
25 | use Exception;
26 | use Hprose\TimeoutException;
27 | use swoole_http_client;
28 |
29 | class Transporter {
30 | public $client;
31 | public $uri;
32 | public $size = 0;
33 | public $pool = array();
34 | public $requests = array();
35 | public $cookies = array();
36 | public function __construct(Client $client) {
37 | $this->client = $client;
38 | $this->uri = $client->uri;
39 | }
40 | public function create() {
41 | $client = $this->client;
42 | if (filter_var($client->host, FILTER_VALIDATE_IP) === false) {
43 | $ip = gethostbyname($client->host);
44 | if ($ip === $client->host) {
45 | throw new Exception('DNS lookup failed');
46 | }
47 | }
48 | else {
49 | $ip = $client->host;
50 | }
51 | $conn = new swoole_http_client($ip, $client->port, $client->ssl);
52 | $this->size++;
53 | return $conn;
54 | }
55 | public function fetch() {
56 | while (!empty($this->pool)) {
57 | $conn = array_pop($this->pool);
58 | if ($conn->isConnected()) {
59 | swoole_timer_clear($conn->timer);
60 | $conn->timer = null;
61 | return $conn;
62 | }
63 | }
64 | return null;
65 | }
66 | public function recycle($conn) {
67 | if ($this->client->keepAlive) {
68 | if (array_search($conn, $this->pool, true) === false) {
69 | $conn->timer = swoole_timer_after($this->client->poolTimeout,
70 | function() use ($conn) {
71 | swoole_timer_clear($conn->timer);
72 | if ($conn->isConnected()) {
73 | $conn->close();
74 | }
75 | });
76 | $this->pool[] = $conn;
77 | }
78 | }
79 | else {
80 | if ($conn->isConnected()) {
81 | $conn->close();
82 | }
83 | }
84 | }
85 | public function clean($conn) {
86 | if (isset($conn->timeoutId)) {
87 | swoole_timer_clear($conn->timeoutId);
88 | unset($conn->timeoutId);
89 | }
90 | }
91 | public function sendNext($conn) {
92 | if (!empty($this->requests)) {
93 | $request = array_pop($this->requests);
94 | $request[] = $conn;
95 | call_user_func_array(array($this, "send"), $request);
96 | }
97 | else {
98 | $this->recycle($conn);
99 | }
100 | }
101 | public function send($request, $future, $context, $conn) {
102 | $self = $this;
103 | $timeout = $context->timeout;
104 | if ($timeout > 0) {
105 | $conn->timeoutId = swoole_timer_after($timeout,
106 | function() use ($self, $future, $conn) {
107 | $future->reject(new TimeoutException('timeout'));
108 | if ($conn->isConnected()) {
109 | $conn->close();
110 | }
111 | });
112 | }
113 | $header = array(
114 | 'Host' => $this->client->host
115 | );
116 | foreach ($this->client->header as $name => $value) {
117 | $header[$name] = $value;
118 | }
119 | if (isset($context->httpHeader) && is_array($context->httpHeader)) {
120 | foreach ($context->httpHeader as $name => $value) {
121 | $header[$name] = $value;
122 | }
123 | }
124 | $conn->setHeaders($header);
125 | $conn->setCookies($this->cookies);
126 | $conn->post($this->client->path, $request,
127 | function($conn) use ($self, $future, $context) {
128 | $self->cookies = $conn->cookies;
129 | $context->httpHeader = $conn->headers;
130 | if ($conn->errCode === 0) {
131 | if ($conn->statusCode == 200) {
132 | $future->resolve($conn->body);
133 | }
134 | else {
135 | $future->reject(new Exception($conn->body));
136 | }
137 | }
138 | else {
139 | $future->reject(new Exception(socket_strerror($conn->errCode)));
140 | }
141 | $self->sendNext($conn);
142 | });
143 | }
144 | public function sendAndReceive($request, $future, stdClass $context) {
145 | $conn = $this->fetch();
146 | if ($conn !== null) {
147 | $this->send($request, $future, $context, $conn);
148 | }
149 | else if ($this->size < $this->client->maxPoolSize) {
150 | $self = $this;
151 | $conn = $this->create();
152 | $conn->on('error', function($conn) use ($future) {
153 | $future->reject(new Exception(socket_strerror($conn->errCode)));
154 | });
155 | $conn->on('close', function($conn) use ($self, $future) {
156 | $self->clean($conn);
157 | if ($conn->errCode !== 0) {
158 | $future->reject(new Exception(socket_strerror($conn->errCode)));
159 | }
160 | else {
161 | $future->reject(new Exception('The server is closed.'));
162 | }
163 | $self->size--;
164 | });
165 | $conn->set(array('keep_alive' => $this->client->keepAlive));
166 | $conn->setHeaders($this->client->header);
167 | $self->send($request, $future, $context, $conn);
168 | }
169 | else {
170 | $this->requests[] = array($request, $future, $context);
171 | }
172 | }
173 |
174 | }
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Server.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole;
23 |
24 | use Exception;
25 |
26 | class Server {
27 | private $server = null;
28 | public function __construct($uri, $mode = SWOOLE_BASE) {
29 | $p = parse_url($uri);
30 | if ($p) {
31 | switch (strtolower($p['scheme'])) {
32 | case 'ws':
33 | case 'wss':
34 | $this->server = new \Hprose\Swoole\WebSocket\Server($uri, $mode);
35 | break;
36 | case 'http':
37 | case 'https':
38 | $this->server = new \Hprose\Swoole\Http\Server($uri, $mode);
39 | break;
40 | case 'tcp':
41 | case 'tcp4':
42 | case 'tcp6':
43 | case 'ssl':
44 | case 'sslv2':
45 | case 'sslv3':
46 | case 'tls':
47 | case 'unix':
48 | $this->server = new \Hprose\Swoole\Socket\Server($uri, $mode);
49 | break;
50 | default:
51 | throw new Exception("Can't support this scheme: {$p['scheme']}");
52 | }
53 | }
54 | else {
55 | throw new \Exception("Can't parse this url: " . $uri);
56 | }
57 | }
58 | public function __call($name, $args) {
59 | return call_user_func_array(array($this->server, $name), $args);
60 | }
61 | public function __set($name, $value) {
62 | $this->server->$name = $value;
63 | }
64 | public function __get($name) {
65 | return $this->server->$name;
66 | }
67 | public function __isset($name) {
68 | return isset($this->server->$name);
69 | }
70 | public function __unset($name) {
71 | unset($this->server->$name);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Socket/Client.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\Socket;
23 |
24 | use stdClass;
25 | use Exception;
26 | use Hprose\Future;
27 |
28 | class Client extends \Hprose\Client {
29 | public $type;
30 | public $host = "";
31 | public $port = 0;
32 | public $fullDuplex = false;
33 | public $maxPoolSize = 10;
34 | public $poolTimeout = 30000;
35 | public $noDelay = true;
36 | public $settings = array();
37 | private $fdtrans;
38 | private $hdtrans;
39 | public function __construct($uris = null) {
40 | parent::__construct($uris);
41 | swoole_async_set(array(
42 | "socket_buffer_size" => 2 * 1024 * 1024 * 1024 - 1,
43 | "socket_dontwait" => false
44 | ));
45 | }
46 | public function getHost() {
47 | return $this->host;
48 | }
49 | public function getPort() {
50 | return $this->port;
51 | }
52 | public function getType() {
53 | return $this->type;
54 | }
55 | public function setFullDuplex($value) {
56 | $this->fullDuplex = $value;
57 | }
58 | public function isFullDuplex() {
59 | return $this->fullDuplex;
60 | }
61 | public function setNoDelay($value) {
62 | $this->noDelay = $value;
63 | }
64 | public function isNoDelay() {
65 | return $this->noDelay;
66 | }
67 | public function setMaxPoolSize($value) {
68 | $this->maxPoolSize = $value;
69 | }
70 | public function getMaxPoolSize() {
71 | return $this->maxPoolSize;
72 | }
73 | public function setPoolTimeout($value) {
74 | $this->poolTimeout = $value;
75 | }
76 | public function getPoolTimeout() {
77 | return $this->poolTimeout;
78 | }
79 | public function set($key, $value) {
80 | $this->settings[$key] = $value;
81 | return $this;
82 | }
83 | protected function setUri($uri) {
84 | parent::setUri($uri);
85 | $p = parse_url($uri);
86 | if ($p) {
87 | switch (strtolower($p['scheme'])) {
88 | case 'tcp':
89 | case 'tcp4':
90 | $this->type = SWOOLE_SOCK_TCP;
91 | $this->host = $p['host'];
92 | $this->port = $p['port'];
93 | break;
94 | case 'tcp6':
95 | $this->type = SWOOLE_SOCK_TCP6;
96 | $this->host = $p['host'];
97 | $this->port = $p['port'];
98 | break;
99 | case 'ssl':
100 | case 'sslv2':
101 | case 'sslv3':
102 | case 'tls':
103 | $this->type = SWOOLE_SOCK_TCP | SWOOLE_SSL;
104 | $this->host = $p['host'];
105 | $this->port = $p['port'];
106 | break;
107 | case 'unix':
108 | $this->type = SWOOLE_UNIX_STREAM;
109 | $this->host = $p['path'];
110 | $this->port = 0;
111 | break;
112 | default:
113 | throw new Exception("Only support tcp, tcp4, tcp6 or unix scheme");
114 | }
115 | if ((($this->type === SWOOLE_SOCK_TCP) ||
116 | ($this->type === SWOOLE_SOCK_TCP | SWOOLE_SSL)) &&
117 | (filter_var($this->host, FILTER_VALIDATE_IP) === false)) {
118 | $ip = gethostbyname($this->host);
119 | if ($ip === $this->host) {
120 | throw new Exception('DNS lookup failed');
121 | }
122 | else {
123 | $this->host = $ip;
124 | }
125 | }
126 | $this->close();
127 | }
128 | else {
129 | throw new Exception("Can't parse this uri: " . $uri);
130 | }
131 | }
132 | public function close() {
133 | if (isset($this->fdtrans)) {
134 | $this->fdtrans->close();
135 | }
136 | if (isset($this->hdtrans)) {
137 | $this->hdtrans->close();
138 | }
139 | }
140 | protected function wait($interval, $callback) {
141 | $future = new Future();
142 | swoole_timer_after($interval * 1000, function() use ($future, $callback) {
143 | Future\sync($callback)->fill($future);
144 | });
145 | return $future;
146 | }
147 | protected function sendAndReceive($request, stdClass $context) {
148 | $future = new Future();
149 | if ($this->fullDuplex) {
150 | if (($this->fdtrans === null) || ($this->fdtrans->uri !== $this->uri)) {
151 | $this->fdtrans = new FullDuplexTransporter($this);
152 | }
153 | $this->fdtrans->sendAndReceive($request, $future, $context);
154 | }
155 | else {
156 | if (($this->hdtrans === null) || ($this->hdtrans->uri !== $this->uri)) {
157 | $this->hdtrans = new HalfDuplexTransporter($this);
158 | }
159 | $this->hdtrans->sendAndReceive($request, $future, $context);
160 | }
161 | if ($context->oneway) {
162 | $future->resolve(null);
163 | }
164 | return $future;
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Socket/FullDuplexTransporter.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\Socket;
23 |
24 | use stdClass;
25 | use Exception;
26 | use Hprose\TimeoutException;
27 |
28 | class FullDuplexTransporter extends Transporter {
29 | private $nextid = 0;
30 | public function fetch() {
31 | while (!empty($this->pool)) {
32 | $conn = array_pop($this->pool);
33 | if ($conn->isConnected()) {
34 | if ($conn->count === 0) {
35 | if (isset($conn->timer)) {
36 | swoole_timer_clear($conn->timer);
37 | unset($conn->timer);
38 | }
39 | $conn->wakeup();
40 | }
41 | return $conn;
42 | }
43 | }
44 | return null;
45 | }
46 | private function init($conn) {
47 | $self = $this;
48 | $conn->count = 0;
49 | $conn->futures = array();
50 | $conn->timeoutIds = array();
51 | $conn->onreceive = function($conn, $data, $id) use ($self) {
52 | if (isset($conn->futures[$id])) {
53 | $future = $conn->futures[$id];
54 | $self->clean($conn, $id);
55 | if ($conn->count === 0) {
56 | $self->recycle($conn);
57 | }
58 | $future->resolve($data);
59 | }
60 | };
61 | $conn->on('close', function($conn) use ($self) {
62 | $futures = $conn->futures;
63 | if ($conn->errCode !== 0) {
64 | $error = new Exception(socket_strerror($conn->errCode));
65 | }
66 | else {
67 | $error = new Exception('The server is closed.');
68 | }
69 | foreach ($futures as $id => $future) {
70 | $self->clean($conn, $id);
71 | $future->reject($error);
72 | }
73 | $self->size--;
74 | });
75 | }
76 | public function recycle($conn) {
77 | $conn->sleep();
78 | $conn->timer = swoole_timer_after($this->client->poolTimeout, function() use ($conn) {
79 | if (isset($conn->timer)) {
80 | swoole_timer_clear($conn->timer);
81 | unset($conn->timer);
82 | }
83 | if ($conn->isConnected()) {
84 | $conn->close();
85 | }
86 | });
87 | }
88 | public function clean($conn, $id) {
89 | if (isset($conn->timeoutIds[$id])) {
90 | swoole_timer_clear($conn->timeoutIds[$id]);
91 | unset($conn->timeoutIds[$id]);
92 | }
93 | unset($conn->futures[$id]);
94 | $conn->count--;
95 | $this->sendNext($conn);
96 | }
97 | public function sendNext($conn) {
98 | if ($conn->count < 10) {
99 | if (!empty($this->requests)) {
100 | $request = array_pop($this->requests);
101 | $request[] = $conn;
102 | call_user_func_array(array($this, "send"), $request);
103 | }
104 | else {
105 | if (array_search($conn, $this->pool, true) === false) {
106 | $this->pool[] = $conn;
107 | }
108 | }
109 | }
110 | }
111 | public function send($request, $future, $id, $context, $conn) {
112 | $self = $this;
113 | $timeout = $context->timeout;
114 | if ($timeout > 0) {
115 | $conn->timeoutIds[$id] = swoole_timer_after($timeout, function() use ($self, $future, $id, $conn) {
116 | $self->clean($conn, $id);
117 | if ($conn->count === 0) {
118 | $self->recycle($conn);
119 | }
120 | $future->reject(new TimeoutException('timeout'));
121 | });
122 | }
123 | $conn->count++;
124 | $conn->futures[$id] = $future;
125 | $header = pack('NN', strlen($request) | 0x80000000, $id);
126 | $conn->send($header);
127 | $conn->send($request);
128 | $this->sendNext($conn);
129 | }
130 | private function getNextId() {
131 | return ($this->nextid < 0x7FFFFFFF) ? $this->nextid++ : $this->nextid = 0;
132 | }
133 | public function sendAndReceive($request, $future, stdClass $context) {
134 | $conn = $this->fetch();
135 | $id = $this->getNextId();
136 | if ($conn !== null) {
137 | $this->send($request, $future, $id, $context, $conn);
138 | }
139 | else if ($this->size < $this->client->maxPoolSize) {
140 | $self = $this;
141 | $conn = $this->create();
142 | $self->init($conn);
143 | $conn->on('error', function($conn) use ($self, $future) {
144 | $self->size--;
145 | $future->reject(new Exception(socket_strerror($conn->errCode)));
146 | });
147 | $conn->on('connect', function($conn) use ($self, $request, $future, $id, $context) {
148 | $self->send($request, $future, $id, $context, $conn);
149 | });
150 | $conn->connect($this->client->host, $this->client->port);
151 | }
152 | else {
153 | $this->requests[] = array($request, $future, $id, $context);
154 | }
155 | }
156 | }
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Socket/HalfDuplexTransporter.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\Socket;
23 |
24 | use stdClass;
25 | use Exception;
26 | use Hprose\TimeoutException;
27 |
28 | class HalfDuplexTransporter extends Transporter {
29 | public function fetch() {
30 | while (!empty($this->pool)) {
31 | $conn = array_pop($this->pool);
32 | if ($conn->isConnected()) {
33 | if (isset($conn->timer)) {
34 | swoole_timer_clear($conn->timer);
35 | unset($conn->timer);
36 | }
37 | $conn->wakeup();
38 | return $conn;
39 | }
40 | }
41 | return null;
42 | }
43 | public function recycle($conn) {
44 | if (array_search($conn, $this->pool, true) === false) {
45 | $conn->sleep();
46 | $conn->timer = swoole_timer_after($this->client->poolTimeout, function() use ($conn) {
47 | if (isset($conn->timer)) {
48 | swoole_timer_clear($conn->timer);
49 | unset($conn->timer);
50 | }
51 | if ($conn->isConnected()) {
52 | $conn->close();
53 | }
54 | });
55 | $this->pool[] = $conn;
56 | }
57 | }
58 | public function clean($conn) {
59 | if (isset($conn->timeoutId)) {
60 | swoole_timer_clear($conn->timeoutId);
61 | unset($conn->timeoutId);
62 | }
63 | }
64 | public function sendNext($conn) {
65 | if (!empty($this->requests)) {
66 | $request = array_pop($this->requests);
67 | $request[] = $conn;
68 | call_user_func_array(array($this, "send"), $request);
69 | }
70 | else {
71 | $this->recycle($conn);
72 | }
73 | }
74 | public function send($request, $future, $context, $conn) {
75 | $self = $this;
76 | $timeout = $context->timeout;
77 | if ($timeout > 0) {
78 | $conn->timeoutId = swoole_timer_after($timeout, function() use ($self, $future, $conn) {
79 | $future->reject(new TimeoutException('timeout'));
80 | if ($conn->isConnected()) {
81 | $conn->close();
82 | }
83 | });
84 | }
85 | $conn->onreceive = function($conn, $data) use ($self, $future) {
86 | $self->clean($conn);
87 | $self->sendNext($conn);
88 | $future->resolve($data);
89 | };
90 | $conn->onclose = function($conn) use ($self, $future) {
91 | $self->clean($conn);
92 | if ($conn->errCode !== 0) {
93 | $future->reject(new Exception(socket_strerror($conn->errCode)));
94 | }
95 | else {
96 | $future->reject(new Exception('The server is closed.'));
97 | }
98 | $self->size--;
99 | };
100 | $header = pack('N', strlen($request));
101 | $conn->send($header);
102 | $conn->send($request);
103 | }
104 | public function sendAndReceive($request, $future, stdClass $context) {
105 | $conn = $this->fetch();
106 | if ($conn !== null) {
107 | $this->send($request, $future, $context, $conn);
108 | }
109 | else if ($this->size < $this->client->maxPoolSize) {
110 | $self = $this;
111 | $conn = $this->create();
112 | $conn->onclose = function($conn) use ($self, $future) {
113 | $self->clean($conn);
114 | if ($conn->errCode !== 0) {
115 | $future->reject(new Exception(socket_strerror($conn->errCode)));
116 | }
117 | else {
118 | $future->reject(new Exception('The server is closed.'));
119 | }
120 | };
121 | $conn->on('close', function($conn) {
122 | $onclose = $conn->onclose;
123 | $onclose($conn);
124 | });
125 | $conn->on('error', function($conn) use ($self, $future) {
126 | $self->size--;
127 | $future->reject(new Exception(socket_strerror($conn->errCode)));
128 | });
129 | $conn->on('connect', function($conn) use ($self, $request, $future, $context) {
130 | $self->send($request, $future, $context, $conn);
131 | });
132 | $conn->connect($this->client->host, $this->client->port);
133 | }
134 | else {
135 | $this->requests[] = array($request, $future, $context);
136 | }
137 | }
138 | }
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Socket/Server.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\Socket;
23 |
24 | use stdClass;
25 | use Exception;
26 | use swoole_server;
27 |
28 | class Server extends Service {
29 | public $server;
30 | public $settings = array();
31 | public $noDelay = true;
32 | private $type;
33 | private function parseUrl($uri) {
34 | $result = new stdClass();
35 | $p = parse_url($uri);
36 | if ($p) {
37 | switch (strtolower($p['scheme'])) {
38 | case 'tcp':
39 | case 'tcp4':
40 | $result->type = SWOOLE_SOCK_TCP;
41 | $result->host = $p['host'];
42 | $result->port = $p['port'];
43 | break;
44 | case 'tcp6':
45 | $result->type = SWOOLE_SOCK_TCP6;
46 | $result->host = $p['host'];
47 | $result->port = $p['port'];
48 | break;
49 | case 'ssl':
50 | case 'sslv2':
51 | case 'sslv3':
52 | case 'tls':
53 | $result->type = SWOOLE_SOCK_TCP | SWOOLE_SSL;
54 | $result->host = $p['host'];
55 | $result->port = $p['port'];
56 | break;
57 | case 'unix':
58 | $result->type = SWOOLE_UNIX_STREAM;
59 | $result->host = $p['path'];
60 | $result->port = 0;
61 | break;
62 | default:
63 | throw new Exception("Can't support this scheme: {$p['scheme']}");
64 | }
65 | }
66 | else {
67 | throw new Exception("Can't parse this uri: " . $uri);
68 | }
69 | return $result;
70 | }
71 | public function __construct($uri, $mode = SWOOLE_BASE) {
72 | parent::__construct();
73 | $url = $this->parseUrl($uri);
74 | $this->type = $url->type;
75 | $this->server = new swoole_server($url->host, $url->port, $mode, $url->type);
76 | }
77 | public function setNoDelay($value) {
78 | $this->noDelay = $value;
79 | }
80 | public function isNoDelay() {
81 | return $this->noDelay;
82 | }
83 | public function set($settings) {
84 | $this->settings = array_replace($this->settings, $settings);
85 | }
86 | public function on($name, $callback) {
87 | $this->server->on($name, $callback);
88 | }
89 | public function addListener($uri) {
90 | $url = $this->parseUrl($uri);
91 | $this->server->addListener($url->host, $url->port, $url->type);
92 | }
93 | public function listen($host, $port, $type = SWOOLE_SOCK_TCP) {
94 | return $this->server->listen($host, $port, $type);
95 | }
96 | public function start() {
97 | if ($this->type !== SWOOLE_UNIX_STREAM) {
98 | $this->settings['open_tcp_nodelay'] = $this->noDelay;
99 | }
100 | $this->settings['open_eof_check'] = false;
101 | $this->settings['open_length_check'] = false;
102 | $this->settings['open_eof_split'] = false;
103 | $this->server->set($this->settings);
104 | $this->socketHandle($this->server);
105 | $this->server->start();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Socket/Service.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\Socket;
23 |
24 | use stdClass;
25 | use Exception;
26 | use Throwable;
27 | use Hprose\Swoole\Timer;
28 |
29 | class Service extends \Hprose\Service {
30 | const MAX_PACK_LEN = 0x200000;
31 | public $onAccept = null;
32 | public $onClose = null;
33 | public function __construct() {
34 | parent::__construct();
35 | $this->timer = new Timer();
36 | }
37 | private function send($server, $socket, $data) {
38 | if ($server->exist($socket)) {
39 | return $server->send($socket, $data);
40 | }
41 | return false;
42 | }
43 | public function socketSend($server, $socket, $data, $id) {
44 | $dataLength = strlen($data);
45 | if ($id === null) {
46 | $this->send($server, $socket, pack("N", $dataLength));
47 | }
48 | else {
49 | $this->send($server, $socket, pack("NN", $dataLength | 0x80000000, $id));
50 | }
51 | if ($dataLength <= self::MAX_PACK_LEN) {
52 | return $this->send($server, $socket, $data);
53 | }
54 | else {
55 | for ($i = 0; $i < $dataLength; $i += self::MAX_PACK_LEN) {
56 | if (!$this->send($server, $socket, substr($data, $i, min($dataLength - $i, self::MAX_PACK_LEN)))) {
57 | return false;
58 | }
59 | }
60 | return true;
61 | }
62 | }
63 | public function getOnReceive() {
64 | $self = $this;
65 | $bytes = '';
66 | $headerLength = 4;
67 | $dataLength = -1;
68 | $id = null;
69 | return function($server, $socket, $fromid, $data)
70 | use ($self, &$bytes, &$headerLength, &$dataLength, &$id) {
71 | $bytes .= $data;
72 | while (true) {
73 | $length = strlen($bytes);
74 | if (($dataLength < 0) && ($length >= $headerLength)) {
75 | list(, $dataLength) = unpack('N', substr($bytes, 0, 4));
76 | if (($dataLength & 0x80000000) !== 0) {
77 | $dataLength &= 0x7FFFFFFF;
78 | $headerLength = 8;
79 | }
80 | }
81 | if (($headerLength === 8) && ($id === null) && ($length >= $headerLength)) {
82 | list(, $id) = unpack('N', substr($bytes, 4, 4));
83 | }
84 | if (($dataLength >= 0) && (($length - $headerLength) >= $dataLength)) {
85 | $context = new stdClass();
86 | $context->server = $server;
87 | $context->socket = $socket;
88 | $context->fd = $socket;
89 | $context->fromid = $fromid;
90 | $context->userdata = new stdClass();
91 | $data = substr($bytes, $headerLength, $dataLength);
92 | $self->userFatalErrorHandler = function($error) use ($self, $server, $socket, $id, $context) {
93 | $self->socketSend($server, $socket, $self->endError($error, $context), $id);
94 | };
95 | $self->defaultHandle($data, $context)->then(function($data) use ($self, $server, $socket, $id) {
96 | $self->socketSend($server, $socket, $data, $id);
97 | });
98 | $bytes = substr($bytes, $headerLength + $dataLength);
99 | $id = null;
100 | $headerLength = 4;
101 | $dataLength = -1;
102 | }
103 | else {
104 | break;
105 | }
106 | }
107 | };
108 | }
109 | public function socketHandle($server) {
110 | $self = $this;
111 | $onReceives = array();
112 | $server->on('connect', function($server, $socket, $fromid) use ($self, &$onReceives) {
113 | $onReceives[$socket] = $self->getOnReceive();
114 | try {
115 | $onAccept = $self->onAccept;
116 | if (is_callable($onAccept)) {
117 | $context = new stdClass();
118 | $context->server = $server;
119 | $context->socket = $socket;
120 | $context->fd = $socket;
121 | $context->fromid = $fromid;
122 | $context->userdata = new stdClass();
123 | call_user_func($onAccept, $context);
124 | }
125 | }
126 | catch (Exception $e) { $server->close($socket); }
127 | catch (Throwable $e) { $server->close($socket); }
128 | });
129 | $server->on('close', function($server, $socket, $fromid) use ($self, &$onReceives) {
130 | unset($onReceives[$socket]);
131 | try {
132 | $onClose = $self->onClose;
133 | if (is_callable($onClose)) {
134 | $context = new stdClass();
135 | $context->server = $server;
136 | $context->socket = $socket;
137 | $context->fd = $socket;
138 | $context->fromid = $fromid;
139 | $context->userdata = new stdClass();
140 | call_user_func($onClose, $context);
141 | }
142 | }
143 | catch (Exception $e) {}
144 | catch (Throwable $e) {}
145 | });
146 | $server->on("receive", function ($server, $socket, $fromid, $data) use(&$onReceives) {
147 | if (isset($onReceives[$socket])) {
148 | $onReceive = $onReceives[$socket];
149 | $onReceive($server, $socket, $fromid, $data);
150 | }
151 | else {
152 | $server->close($socket, true);
153 | }
154 | });
155 | }
156 | }
157 |
158 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Socket/Transporter.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\Socket;
23 |
24 | use swoole_client;
25 |
26 | abstract class Transporter {
27 | public $client;
28 | public $uri;
29 | public $size = 0;
30 | public $pool = array();
31 | public $requests = array();
32 | public function __construct(Client $client) {
33 | $this->client = $client;
34 | $this->uri = $client->uri;
35 | }
36 | public function __destruct() {
37 | $this->close();
38 | }
39 | public function close() {
40 | foreach ($this->pool as $conn) {
41 | if (isset($conn->timer)) {
42 | swoole_timer_clear($conn->timer);
43 | unset($conn->timer);
44 | }
45 | if ($conn->isConnected()) {
46 | $conn->close();
47 | }
48 | }
49 | }
50 | public function setReceiveEvent($conn) {
51 | $bytes = '';
52 | $headerLength = 4;
53 | $dataLength = -1;
54 | $id = null;
55 | $conn->on('receive', function($conn, $chunk) use (&$bytes, &$headerLength, &$dataLength, &$id) {
56 | $bytes .= $chunk;
57 | while (true) {
58 | $length = strlen($bytes);
59 | if (($dataLength < 0) && ($length >= $headerLength)) {
60 | list(, $dataLength) = unpack('N', substr($bytes, 0, 4));
61 | if (($dataLength & 0x80000000) !== 0) {
62 | $dataLength &= 0x7FFFFFFF;
63 | $headerLength = 8;
64 | }
65 | }
66 | if (($headerLength === 8) && ($id === null) && ($length >= $headerLength)) {
67 | list(, $id) = unpack('N', substr($bytes, 4, 4));
68 | }
69 | if (($dataLength >= 0) && (($length - $headerLength) >= $dataLength)) {
70 | $onreceive = $conn->onreceive;
71 | $onreceive($conn, substr($bytes, $headerLength, $dataLength), $id);
72 | $bytes = substr($bytes, $headerLength + $dataLength);
73 | $id = null;
74 | $headerLength = 4;
75 | $dataLength = -1;
76 | }
77 | else {
78 | break;
79 | }
80 | }
81 | });
82 | }
83 | public function create() {
84 | $client = $this->client;
85 | $conn = new swoole_client($client->type, SWOOLE_SOCK_ASYNC);
86 | // The type is changed after new swoole_client in old version swoole.
87 | // The new version swoole is fixed this bug.
88 | $client->type &= 0xFF;
89 | if ($client->type !== SWOOLE_UNIX_STREAM) {
90 | $client->settings['open_tcp_nodelay'] = $client->noDelay;
91 | }
92 | $client->settings['open_eof_check'] = false;
93 | $client->settings['open_length_check'] = false;
94 | $client->settings['open_eof_split'] = false;
95 | $conn->set($client->settings);
96 | $this->setReceiveEvent($conn);
97 | $this->size++;
98 | return $conn;
99 | }
100 | }
--------------------------------------------------------------------------------
/src/Hprose/Swoole/Timer.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole;
23 |
24 | class Timer {
25 | public function setTimeout($callback, $delay) {
26 | return swoole_timer_after($delay, $callback);
27 | }
28 | public function clearTimeout($timerid) {
29 | return swoole_timer_clear($timerid);
30 | }
31 | public function setImmediate($callback) {
32 | swoole_event_defer($callback);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/WebSocket/Client.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\WebSocket;
23 |
24 | use stdClass;
25 | use Exception;
26 | use Hprose\Future;
27 | use Hprose\TimeoutException;
28 | use swoole_http_client;
29 |
30 | class Client extends \Hprose\Client {
31 | public $type;
32 | public $host = '';
33 | public $ip = '';
34 | public $port = 80;
35 | public $ssl = false;
36 | public $keepAlive = true;
37 | public $keepAliveTimeout = 300;
38 | private $header = array();
39 | private $id = 0;
40 | private $count = 0;
41 | private $futures = array();
42 | private $requests = array();
43 | private $ready = null;
44 | private $connecting = false;
45 | private $ws = null;
46 | public function __construct($uris = null) {
47 | parent::__construct($uris);
48 | }
49 | public function setHeader($name, $value) {
50 | $lname = strtolower($name);
51 | if ($lname != 'content-type' &&
52 | $lname != 'content-length' &&
53 | $lname != 'host') {
54 | if ($value) {
55 | $this->header[$name] = $value;
56 | }
57 | else {
58 | unset($this->header[$name]);
59 | }
60 | }
61 | }
62 | public function setKeepAlive($keepAlive = true) {
63 | $this->keepAlive = $keepAlive;
64 | $this->header['Connection'] = $keepAlive ? 'keep-alive' : 'close';
65 | if ($keepAlive) {
66 | $this->header['Keep-Ailve'] = $this->keepAliveTimeout;
67 | }
68 | else {
69 | unset($this->header['Keep-Ailve']);
70 | }
71 | }
72 | public function isKeepAlive() {
73 | return $this->keepAlive;
74 | }
75 | public function setKeepAliveTimeout($timeout) {
76 | $this->keepAliveTimeout = $timeout;
77 | if ($this->keepAlive) {
78 | $this->header['Keep-Ailve'] = $timeout;
79 | }
80 | }
81 | public function getKeepAliveTimeout() {
82 | return $this->keepAliveTimeout;
83 | }
84 | public function getHost() {
85 | return $this->host;
86 | }
87 | public function getPort() {
88 | return $this->port;
89 | }
90 | public function isSSL() {
91 | return $this->ssl;
92 | }
93 | protected function setUri($uri) {
94 | parent::setUri($uri);
95 | $p = parse_url($uri);
96 | if ($p) {
97 | switch (strtolower($p['scheme'])) {
98 | case 'ws':
99 | $this->host = $p['host'];
100 | $this->port = isset($p['port']) ? $p['port'] : 80;
101 | $this->path = isset($p['path']) ? $p['path'] : '/';
102 | $this->ssl = false;
103 | break;
104 | case 'wss':
105 | $this->host = $p['host'];
106 | $this->port = isset($p['port']) ? $p['port'] : 443;
107 | $this->path = isset($p['path']) ? $p['path'] : '/';
108 | $this->ssl = true;
109 | break;
110 | default:
111 | throw new Exception("Only support ws and wss scheme");
112 | }
113 | }
114 | else {
115 | throw new Exception("Can't parse this uri: " . $uri);
116 | }
117 | $this->header['Host'] = $this->host;
118 | $this->header['Connection'] = $this->keepAlive ? 'keep-alive' : 'close';
119 | if ($this->keepAlive) {
120 | $this->header['Keep-Ailve'] = $this->keepAliveTimeout;
121 | }
122 | if (filter_var($this->host, FILTER_VALIDATE_IP) === false) {
123 | $ip = gethostbyname($this->host);
124 | if ($ip === $this->host) {
125 | throw new Exception('DNS lookup failed');
126 | }
127 | else {
128 | $this->ip = $ip;
129 | }
130 | }
131 | else {
132 | $this->ip = $this->host;
133 | }
134 | }
135 | public function close() {
136 | if ($this->ws !== null && $this->ws->isConnected()) {
137 | $this->ws->close();
138 | $this->ws = null;
139 | }
140 | }
141 | protected function wait($interval, $callback) {
142 | $future = new Future();
143 | swoole_timer_after($interval * 1000, function() use ($future, $callback) {
144 | Future\sync($callback)->fill($future);
145 | });
146 | return $future;
147 | }
148 | private function getNextId() {
149 | return ($this->id < 0x7FFFFFFF) ? ++$this->id : $this->id = 0;
150 | }
151 | private function connect() {
152 | $this->connecting = true;
153 | $connecting = &$this->connecting;
154 | $this->ready = new Future();
155 | $ready = &$this->ready;
156 | $futures = &$this->futures;
157 | $count = &$this->count;
158 | $requests = &$this->requests;
159 | $ws = new swoole_http_client($this->ip, $this->port, $this->ssl);
160 | $ws->on('error', function($ws) use (&$futures, &$count) {
161 | $error = new Exception(socket_strerror($ws->errCode));
162 | foreach ($futures as $future) {
163 | $future->reject($error);
164 | }
165 | $futures = array();
166 | $count = 0;
167 | });
168 | $buffer = '';
169 | $self = $this;
170 | $ws->on('message', function($ws, $frame) use ($self, &$buffer, &$futures, &$count, &$requests, &$ready) {
171 | if ($frame->finish) {
172 | $data = $buffer . $frame->data;
173 | $buffer = '';
174 | list(, $id) = unpack('N', substr($data, 0, 4));
175 | if (isset($futures[$id])) {
176 | $future = $futures[$id];
177 | unset($futures[$id]);
178 | --$count;
179 | $future->resolve(substr($data, 4));
180 | }
181 | if (($count < 100) && count($requests) > 0) {
182 | ++$count;
183 | $request = array_pop($requests);
184 | $ready->then(function() use ($ws, $request, &$futures) {
185 | $id = $request[0];
186 | $data = pack('N', $id) . $request[1];
187 | if ($ws->push($data, WEBSOCKET_OPCODE_BINARY, true) === false) {
188 | if (isset($futures[$id])) {
189 | $error = new Exception(socket_strerror($ws->errCode));
190 | $futures[$id]->reject($error);
191 | }
192 | }
193 | });
194 | }
195 | if ($count === 0) {
196 | if (!$self->keepAlive) $ws->close();
197 | }
198 | }
199 | else {
200 | $buffer .= $frame->data;
201 | }
202 | });
203 | $ws->set(array('keep_alive' => $this->keepAlive,
204 | 'timeout' => $this->timeout / 1000));
205 | $ws->setHeaders($this->header);
206 | $this->ws = $ws;
207 | $this->ws->upgrade($this->path, function() use (&$connecting, &$ready) {
208 | $connecting = false;
209 | $ready->resolve(null);
210 | });
211 | }
212 | protected function sendAndReceive($request, stdClass $context) {
213 | $future = new Future();
214 | $id = $this->getNextId();
215 | $count = &$this->count;
216 | $futures = &$this->futures;
217 | $futures[$id] = $future;
218 | if ($context->timeout > 0) {
219 | $timeoutFuture = new Future();
220 | $timer = swoole_timer_after($context->timeout, function() use ($timeoutFuture) {
221 | $timeoutFuture->reject(new TimeoutException('timeout'));
222 | });
223 | $future->whenComplete(function() use ($timer) {
224 | swoole_timer_clear($timer);
225 | })->fill($timeoutFuture);
226 | $future = $timeoutFuture->catchError(function($e) use (&$count, &$futures, $id) {
227 | unset($futures[$id]);
228 | --$count;
229 | throw $e;
230 | }, function($e) {
231 | return $e instanceof TimeoutException;
232 | });
233 |
234 | }
235 | if (!$this->connecting && ($this->ws === null || !$this->ws->isConnected())) {
236 | $this->connect();
237 | }
238 | $ws = $this->ws;
239 | if ($count < 100) {
240 | ++$count;
241 | $this->ready->then(function() use ($ws, $id, $request, &$futures) {
242 | $data = pack('N', $id) . $request;
243 | if ($ws->push($data, WEBSOCKET_OPCODE_BINARY, true) === false) {
244 | if (isset($futures[$id])) {
245 | $error = new Exception(socket_strerror($ws->errCode));
246 | $futures[$id]->reject($error);
247 | }
248 | }
249 | });
250 | }
251 | else {
252 | $this->requests[] = array($id, $request);
253 | }
254 | if ($context->oneway) {
255 | $future->resolve(null);
256 | }
257 | return $future;
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/WebSocket/Server.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\WebSocket;
23 |
24 | use stdClass;
25 | use Exception;
26 | use swoole_websocket_server;
27 |
28 | class Server extends Service {
29 | public $server;
30 | public $settings = array();
31 | private function parseUrl($uri) {
32 | $result = new stdClass();
33 | $p = parse_url($uri);
34 | if ($p) {
35 | switch (strtolower($p['scheme'])) {
36 | case 'ws':
37 | $result->host = $p['host'];
38 | $result->port = isset($p['port']) ? $p['port'] : 80;
39 | $result->type = SWOOLE_SOCK_TCP;
40 | break;
41 | case 'wss':
42 | $result->host = $p['host'];
43 | $result->port = isset($p['port']) ? $p['port'] : 443;
44 | $result->type = SWOOLE_SOCK_TCP | SWOOLE_SSL;
45 | break;
46 | default:
47 | throw new Exception("Can't support this scheme: {$p['scheme']}");
48 | }
49 | }
50 | else {
51 | throw new Exception("Can't parse this uri: $uri");
52 | }
53 | return $result;
54 | }
55 | public function __construct($uri, $mode = SWOOLE_BASE) {
56 | parent::__construct();
57 | $url = $this->parseUrl($uri);
58 | $this->server = new swoole_websocket_server($url->host, $url->port, $mode, $url->type);
59 | }
60 | public function set($settings) {
61 | $this->settings = array_replace($this->settings, $settings);
62 | }
63 | public function on($name, $callback) {
64 | $this->server->on($name, $callback);
65 | }
66 | public function addListener($uri) {
67 | $url = $this->parseUrl($uri);
68 | $this->server->addListener($url->host, $url->port);
69 | }
70 | public function listen($host, $port, $type) {
71 | return $this->server->listen($host, $port, $type);
72 | }
73 | public function start() {
74 | if (is_array($this->settings) && !empty($this->settings)) {
75 | $this->server->set($this->settings);
76 | }
77 | $this->wsHandle($this->server);
78 | $this->httpHandle($this->server);
79 | $this->server->start();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Hprose/Swoole/WebSocket/Service.php:
--------------------------------------------------------------------------------
1 | *
19 | * *
20 | \**********************************************************/
21 |
22 | namespace Hprose\Swoole\WebSocket;
23 |
24 | use stdClass;
25 | use Exception;
26 | use Throwable;
27 | use Hprose\Swoole\Timer;
28 |
29 | class Service extends \Hprose\Swoole\Http\Service {
30 | public $onAccept = null;
31 | public $onClose = null;
32 | public function __construct() {
33 | parent::__construct();
34 | $this->timer = new Timer();
35 | }
36 | /*private*/ function wsPush($server, $fd, $data) {
37 | $dataLength = strlen($data);
38 | if ($dataLength <= self::MAX_PACK_LEN) {
39 | return $server->exist($fd) &&
40 | $server->push($fd, $data, WEBSOCKET_OPCODE_BINARY, true);
41 | }
42 | else {
43 |
44 | for ($i = 0; $i < $dataLength; $i += self::MAX_PACK_LEN) {
45 | $chunkLength = min($dataLength - $i, self::MAX_PACK_LEN);
46 | $chunk = substr($data, $i, $chunkLength);
47 | $finish = ($dataLength - $i === $chunkLength);
48 | if (!($server->exist($fd) &&
49 | $server->push($fd, $chunk, WEBSOCKET_OPCODE_BINARY, $finish))) {
50 | return false;
51 | }
52 | }
53 | return true;
54 | }
55 | }
56 | public function onMessage($server, $fd, $data, $context = null) {
57 | $id = substr($data, 0, 4);
58 | $request = substr($data, 4);
59 |
60 | if ($context === null) $context = new stdClass();
61 | $context->server = $server;
62 | $context->fd = $fd;
63 | $context->id = $id;
64 | $context->userdata = new stdClass();
65 | $self = $this;
66 |
67 | $this->userFatalErrorHandler = function($error)
68 | use ($self, $server, $fd, $id, $context) {
69 | $self->wsPush($server, $fd, $id . $self->endError($error, $context));
70 | };
71 |
72 | $response = $this->defaultHandle($request, $context);
73 |
74 | $response->then(function($response) use ($self, $server, $fd, $id) {
75 | $self->wsPush($server, $fd, $id . $response);
76 | });
77 | }
78 | public function wsHandle($server) {
79 | $self = $this;
80 | $buffers = array();
81 | $server->on('open', function ($server, $request) use ($self, &$buffers) {
82 | $fd = $request->fd;
83 | if (isset($buffers[$fd])) {
84 | unset($buffers[$fd]);
85 | }
86 | try {
87 | $onAccept = $self->onAccept;
88 | if (is_callable($onAccept)) {
89 | $context = new stdClass();
90 | $context->server = $server;
91 | $context->request = $request;
92 | $context->fd = $fd;
93 | $context->userdata = new stdClass();
94 | call_user_func($onAccept, $context);
95 | }
96 | }
97 | catch (Exception $e) { $server->close($fd); }
98 | catch (Throwable $e) { $server->close($fd); }
99 | });
100 | $server->on('close', function ($server, $fd) use ($self, &$buffers) {
101 | if (isset($buffers[$fd])) {
102 | unset($buffers[$fd]);
103 | }
104 | try {
105 | $onClose = $self->onClose;
106 | if (is_callable($onClose)) {
107 | $context = new stdClass();
108 | $context->server = $server;
109 | $context->fd = $fd;
110 | $context->userdata = new stdClass();
111 | call_user_func($onClose, $context);
112 | }
113 | }
114 | catch (Exception $e) {}
115 | catch (Throwable $e) {}
116 | });
117 | $server->on('message', function($server, $frame) use ($self, &$buffers) {
118 | if (isset($buffers[$frame->fd])) {
119 | if ($frame->finish) {
120 | $data = $buffers[$frame->fd] . $frame->data;
121 | unset($buffers[$frame->fd]);
122 | $self->onMessage($server, $frame->fd, $data);
123 | }
124 | else {
125 | $buffers[$frame->fd] .= $frame->data;
126 | }
127 | }
128 | else {
129 | if ($frame->finish) {
130 | $self->onMessage($server, $frame->fd, $frame->data);
131 | }
132 | else {
133 | $buffers[$frame->fd] = $frame->data;
134 | }
135 | }
136 | });
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/tests/Bootstrap.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | .
5 |
6 |
7 |
8 |
9 | ..
10 |
11 | ../vendor/
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------