├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── src ├── Client.php ├── Error.php ├── Exception ├── RpcResponseException.php └── RpcUnexpectedValueException.php ├── Install.php ├── JsonParser.php ├── Protocol └── RpcTextProtocol.php ├── config └── plugin │ └── tinywan │ └── rpc │ ├── app.php │ └── process.php └── function.php /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | vendor 3 | .idea 4 | .vscode 5 | .phpunit* 6 | composer.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ShaoBo Wan(無尘) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple rpc service for webman plugin 2 | 3 | [![Latest Stable Version](http://poser.pugx.org/tinywan/rpc/v)](https://packagist.org/packages/tinywan/rpc) 4 | [![Total Downloads](http://poser.pugx.org/tinywan/rpc/downloads)](https://packagist.org/packages/tinywan/rpc) 5 | [![Latest Unstable Version](http://poser.pugx.org/tinywan/rpc/v/unstable)](https://packagist.org/packages/tinywan/rpc) 6 | [![License](http://poser.pugx.org/tinywan/rpc/license)](https://packagist.org/packages/tinywan/rpc) 7 | [![PHP Version Require](http://poser.pugx.org/tinywan/rpc/require/php)](https://packagist.org/packages/tinywan/rpc) 8 | 9 | ## 安装 10 | 11 | ```shell 12 | composer require tinywan/rpc 13 | ``` 14 | 15 | ## 使用 16 | 17 | ### 服务端服务 18 | 19 | 新建 `service/User.php` 服务(目录不存在自行创建) 20 | ```php 21 | namespace service; 22 | class User 23 | { 24 | public function get($args) 25 | { 26 | return response_rpc_json(0,'获取成功', $args); 27 | } 28 | } 29 | ``` 30 | ### 客户端调用 31 | 32 | ```php 33 | // 建立socket连接到内部推送端口 34 | $resource = stream_socket_client('tcp://127.0.0.1:9512', $errorCode, $errorMessage); 35 | if (false === $resource) { 36 | throw new \Exception('rpc failed to connect: '.$errorMessage); 37 | } 38 | $request = [ 39 | 'class' => 'user', 40 | 'method' => 'get', 41 | 'args' => [ 42 | [ 43 | 'uid' => 2023, 44 | 'username' => 'Tinywan', 45 | ] 46 | ] 47 | ]; 48 | // 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符 49 | fwrite($resource, json_encode($request)."\n"); 50 | // 读取推送结果 51 | $result = fgets($resource, 10240000); 52 | fclose($resource); 53 | // 解析JSON字符串 54 | $result = json_decode($result, true); 55 | var_export($result); 56 | ``` 57 | 58 | 请求响应结果 59 | ```json 60 | { 61 | "code": 0, 62 | "msg": "用户列表", 63 | "data": { 64 | "uid": 2024, 65 | "username": "Tinywan" 66 | } 67 | } 68 | ``` 69 | 70 | 请求响应异常结果 71 | ```json 72 | { 73 | "code": 404, 74 | "msg": "接口调用类不存在", 75 | "data": {} 76 | } 77 | ``` 78 | 79 | ## 在client端发起一个远程伪代码中 80 | 81 | client端调用server端 如果server端的代码为本地则是本地调用,如果server端的代码在另外一台机器就需要远程调用(Rpc协议) 82 | 83 | 1. 服务端通过插件tinywan/rpc自定义进程实现一个文本text协议服务 84 | 2. 客户端将Server和B方法,以及B方法可能带有的参数序列化 85 | 3. 通过stream_socket_client把序列化的消息发送给服务端 86 | 4. 服务端接收消息并反序列化 87 | 5. 通过反射调用调用服务端的Server类下的B方法 88 | 6. 服务端Server类B方法返回的结果序列化 89 | 7. 将返回的序列化结果通过stream_socket_client发送给客户端 90 | 8. 客户端通过反序列化得到结果 91 | 92 | ## 调用编码 93 | ```phpregexp 94 | 1. 接口方法 95 | 包括接口名、方法名 96 | 2. 方法参数 97 | 包括参数类型、参数值 98 | 3. 调用属性 99 | 包括调用属性信息,例如调用附件隐式参数、调用超时时间等 100 | -- 返回编码 -- 101 | 1. 返回结果 102 | 接口方法中定义的返回值 103 | 2. 返回码 104 | 异常返回码 105 | 3. 返回异常信息 106 | 调用异常信息 107 | ``` 108 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tinywan/rpc", 3 | "description": "simple rpc service for webman plugin", 4 | "keywords": ["webman", "rpc", "plugin"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Tinywan", 10 | "email": "756684177@qq.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=7.4", 15 | "workerman/webman-framework": "^1.5||^2.0", 16 | "ext-json": "*", 17 | "tinywan/exception-handler": "^1.5" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "Tinywan\\Rpc\\": "src" 22 | }, 23 | "files": [ 24 | "src/function.php" 25 | ] 26 | }, 27 | "require-dev": { 28 | "workerman/webman": "^1.0", 29 | "phpstan/phpstan": "^1.4", 30 | "friendsofphp/php-cs-fixer": "^3.6" 31 | }, 32 | "repositories": { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | address = $address; 29 | } 30 | 31 | /** 32 | * @return mixed 33 | */ 34 | public function request(array $param) 35 | { 36 | $resource = null; 37 | try { 38 | // 连接阶段超时 39 | $connectTimeout = config('plugin.tinywan.rpc.app.connect_timeout') ?? 3; 40 | $resource = stream_socket_client( 41 | $this->address, 42 | $errno, 43 | $errorMessage, 44 | $connectTimeout 45 | ); 46 | 47 | if (!is_resource($resource)) { 48 | throw new RpcUnexpectedValueException('rpc request failed: ' . $errorMessage); 49 | } 50 | 51 | // 读写超时 52 | $timeout = (int)($param['timeout'] ?? config('plugin.tinywan.rpc.app.request_timeout') ?? 5); 53 | stream_set_timeout($resource, $timeout); 54 | 55 | // 发送请求 56 | fwrite($resource, json_encode($param)."\n"); 57 | 58 | // 实时检测超时 59 | $info = stream_get_meta_data($resource); 60 | if ($info['timed_out']) { 61 | throw new RpcResponseException(Error::make(408, 'rpc request timeout')); 62 | } 63 | 64 | $result = fgets($resource, 10240000); 65 | if ($result){ 66 | return json_decode(trim($result), true); 67 | } 68 | } catch (\Throwable $e) { 69 | throw new RpcUnexpectedValueException('rpc request failed: '.$e->getMessage()); 70 | } finally { 71 | if ($resource && is_resource($resource)) { 72 | fclose($resource); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Error.php: -------------------------------------------------------------------------------- 1 | code = $code; 45 | $instance->message = $message; 46 | $instance->data = $data; 47 | 48 | return $instance; 49 | } 50 | 51 | /** 52 | * @return int 53 | */ 54 | public function getCode(): int 55 | { 56 | return $this->code; 57 | } 58 | 59 | /** 60 | * @return string 61 | */ 62 | public function getMessage(): string 63 | { 64 | return $this->message; 65 | } 66 | 67 | /** 68 | * @return mixed 69 | */ 70 | public function getData() 71 | { 72 | return $this->data; 73 | } 74 | 75 | /** 76 | * @return array 77 | */ 78 | public function jsonSerialize(): array 79 | { 80 | return [ 81 | 'code' => $this->code, 82 | 'message' => $this->message, 83 | 'data' => $this->data, 84 | ]; 85 | } 86 | } -------------------------------------------------------------------------------- /src/Exception/RpcResponseException.php: -------------------------------------------------------------------------------- 1 | getMessage(), $error->getCode()); 23 | $this->error = $error; 24 | } 25 | 26 | public function getError(): Error 27 | { 28 | return $this->error; 29 | } 30 | } -------------------------------------------------------------------------------- /src/Exception/RpcUnexpectedValueException.php: -------------------------------------------------------------------------------- 1 | 'config/plugin/tinywan/rpc', 14 | ]; 15 | 16 | /** 17 | * Install. 18 | * 19 | * @return void 20 | */ 21 | public static function install() 22 | { 23 | static::installByRelation(); 24 | } 25 | 26 | /** 27 | * Uninstall. 28 | * 29 | * @return void 30 | */ 31 | public static function uninstall() 32 | { 33 | self::uninstallByRelation(); 34 | } 35 | 36 | /** 37 | * installByRelation. 38 | * 39 | * @return void 40 | */ 41 | public static function installByRelation() 42 | { 43 | foreach (static::$pathRelation as $source => $dest) { 44 | if ($pos = strrpos($dest, '/')) { 45 | $parent_dir = base_path().'/'.substr($dest, 0, $pos); 46 | if (!is_dir($parent_dir)) { 47 | mkdir($parent_dir, 0777, true); 48 | } 49 | } 50 | // symlink(__DIR__ . "/$source", base_path()."/$dest"); 51 | copy_dir(__DIR__."/$source", base_path()."/$dest"); 52 | echo "Create $dest 53 | "; 54 | } 55 | } 56 | 57 | /** 58 | * uninstallByRelation. 59 | * 60 | * @return void 61 | */ 62 | public static function uninstallByRelation() 63 | { 64 | foreach (static::$pathRelation as $source => $dest) { 65 | $path = base_path()."/$dest"; 66 | if (!is_dir($path) && !is_file($path)) { 67 | continue; 68 | } 69 | echo "Remove $dest 70 | "; 71 | if (is_file($path) || is_link($path)) { 72 | unlink($path); 73 | 74 | continue; 75 | } 76 | remove_dir($path); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/JsonParser.php: -------------------------------------------------------------------------------- 1 | send(json_encode([ 61 | 'code' => $code, 62 | 'msg' => $msg, 63 | 'data' => $data, 64 | ],JSON_UNESCAPED_UNICODE)); 65 | } 66 | } -------------------------------------------------------------------------------- /src/Protocol/RpcTextProtocol.php: -------------------------------------------------------------------------------- 1 | send(call_user_func_array([$instances[$class], $method], $args)); 52 | } catch (Throwable $th) { 53 | Logger::error('RPC Service Exception '.$th->getMessage(), [ 54 | 'error' => $th->getMessage(), 55 | 'file' => $th->getFile(), 56 | 'line' => $th->getLine() 57 | ]); 58 | return JsonParser::encode($connection, 500, $th->getMessage()); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/config/plugin/tinywan/rpc/app.php: -------------------------------------------------------------------------------- 1 | true, 5 | 'server' => [ 6 | 'namespace'=> 'service\\', // 自定义服务命名空间 7 | 'listen_text_address' => 'text://0.0.0.0:9512', // 自定义Text协议地址 8 | ], 9 | 'connect_timeout' => 5, // 超时时间 10 | 'request_timeout' => 5, // 请求超时时间 11 | ]; 12 | -------------------------------------------------------------------------------- /src/config/plugin/tinywan/rpc/process.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'handler'=> \Tinywan\Rpc\Protocol\RpcTextProtocol::class, 6 | 'listen' => config('plugin.tinywan.rpc.app.server.listen_text_address'), 7 | 'count' => 10, // 根据配置文件调整 8 | ] 9 | ]; -------------------------------------------------------------------------------- /src/function.php: -------------------------------------------------------------------------------- 1 | $code, 'msg' => $msg, 'data' => $data],JSON_UNESCAPED_UNICODE); 16 | } 17 | } --------------------------------------------------------------------------------