├── .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 |

Hprose

2 | 3 | # Hprose for Swoole 4 | 5 | [![Build Status](https://travis-ci.org/hprose/hprose-swoole.svg?branch=master)](https://travis-ci.org/hprose/hprose-swoole) 6 | ![Supported PHP versions: 5.3 .. 7.1](https://img.shields.io/badge/php-5.3~7.1-blue.svg) 7 | [![Packagist](https://img.shields.io/packagist/v/hprose/hprose-swoole.svg)](https://packagist.org/packages/hprose/hprose-swoole) 8 | [![Packagist Download](https://img.shields.io/packagist/dm/hprose/hprose-swoole.svg)](https://packagist.org/packages/hprose/hprose-swoole) 9 | [![License](https://img.shields.io/packagist/l/hprose/hprose-swoole.svg)](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 |

Hprose

2 | 3 | # Hprose for Swoole 4 | 5 | [![Build Status](https://travis-ci.org/hprose/hprose-swoole.svg?branch=master)](https://travis-ci.org/hprose/hprose-swoole) 6 | ![Supported PHP versions: 5.3 .. 7.1](https://img.shields.io/badge/php-5.3~7.1-blue.svg) 7 | [![Packagist](https://img.shields.io/packagist/v/hprose/hprose-swoole.svg)](https://packagist.org/packages/hprose/hprose-swoole) 8 | [![Packagist Download](https://img.shields.io/packagist/dm/hprose/hprose-swoole.svg)](https://packagist.org/packages/hprose/hprose-swoole) 9 | [![License](https://img.shields.io/packagist/l/hprose/hprose-swoole.svg)](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 | --------------------------------------------------------------------------------