PHP創(chuàng)建簡單RPC服務(wù)案例詳解
RPC 定義
RPC(Remote Procedure Call)即遠程過程調(diào)用,指被調(diào)用方法的具體實現(xiàn)不在程序運行本地,而是在別的某個地方。主要應(yīng)用于不同的系統(tǒng)之間的遠程通信和相互調(diào)用。
如 A 調(diào)用 B 提供的 remoteAdd 方法:
- 首先A與B之間建立一個TCP連接;
- 然后A把需要調(diào)用的方法名(這里是remoteAdd)以及方法參數(shù)(10, 20)序列化成字節(jié)流發(fā)送出去;
- B接受A發(fā)送過來的字節(jié)流,然后反序列化得到目標(biāo)方法名,方法參數(shù),接著執(zhí)行相應(yīng)的方法調(diào)用(可能是localAdd)并把結(jié)果30返回;
- A接受遠程調(diào)用結(jié)果
有些遠程調(diào)用選擇比較底層的 socket 協(xié)議,有些遠程調(diào)用選擇比較上層的 HTTP 協(xié)議。
遠程調(diào)用的好處:
- 解耦:當(dāng)方法提供者需要對方法內(nèi)實現(xiàn)修改時,調(diào)用者完全感知不到,不用做任何變更;這種方式在跨部門,跨公司合作的時候經(jīng)常用到,并且方法的提供者我們通常稱為:服務(wù)的暴露方
這里使用 PHP Socket 來創(chuàng)建一個服務(wù)端和客戶端,目錄結(jié)構(gòu)如下:
服務(wù)端
<?php class RpcServer { protected $server = null; public function __construct($host, $port, $path) { // 創(chuàng)建一個 Socket 服務(wù) if(($this->server = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) < 0) { exit("socket_create() 失敗的原因是:".socket_strerror($this->server)."\n"); } if(($ret = socket_bind($this->server,$host,$port)) < 0) { exit("socket_bind() 失敗的原因是:".socket_strerror($ret)."\n"); } if(($ret = socket_listen($this->server,3)) < 0) { exit("socket_listen() 失敗的原因是:".socket_strerror($ret)."\n"); } // 判斷 RPC 服務(wù)目錄是否存在 $realPath = realpath(__DIR__ . $path); if ($realPath === false || !file_exists($realPath)) { exit("{$path} error \n"); } do { $client = socket_accept($this->server); if($client) { // 一次性讀取 $buf = socket_read($client, 8024); echo $buf; //解析客戶端發(fā)送過來的協(xié)議 $classRet = preg_match('/Rpc-Class:\s(.*);\r\n/i', $buf, $class); $methodRet = preg_match('/Rpc-Method:\s(.*);\r\n/i', $buf, $method); $paramsRet = preg_match('/Rpc-Params:\s(.*);\r\n/i', $buf, $params); if($classRet && $methodRet) { $class = ucfirst($class[1]); $method = $method[1]; $params = json_decode($params[1], true); $file = $realPath . '/' . $class . '.php'; // 類文件需要和類名一致 $data = ''; // 執(zhí)行結(jié)果 // 判斷類文件是否存在 if(file_exists($file)) { // 引入類文件 require_once $file; // 實例化類 $rfc_obj = new ReflectionClass($class); // 判斷該類指定方法是否存在 if($rfc_obj->hasMethod($method)) { // 執(zhí)行類方法 $rfc_method = $rfc_obj->getMethod($method); $data = $rfc_method->invokeArgs($rfc_obj->newInstance(), [$params]); } else { socket_write($client, 'method error'); } //把運行后的結(jié)果返回給客戶端 socket_write($client, $data); } } else { socket_write($client, 'class or method error'); } // 關(guān)閉客戶端 socket_close($client); } }while(true); } public function __destruct() { socket_close($this->server); } } new RpcServer('127.0.0.1',8080,'./service');
客戶端
<?php class RpcClient { protected $client = null; protected $url_info = []; // 遠程調(diào)用 URL 組成部分 public function __construct($url) { // 解析 URL $this->url_info = parse_url($url); } public function __call($name, $arguments) { // 創(chuàng)建一個客戶端 $this->client = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if(!$this->client) { exit('socket_create() 失敗'); } socket_connect($this->client, $this->url_info['host'], $this->url_info['port']); // 傳遞調(diào)用的類名 $class = basename($this->url_info['path']); // 傳遞調(diào)用的參數(shù) $args = ''; if(isset($arguments[0])) { $args = json_encode($arguments[0]); } // 向服務(wù)端發(fā)送我們自定義的協(xié)議數(shù)據(jù) $proto = "Rpc-Class: {$class};".PHP_EOL ."Rpc-Method: {$name};".PHP_EOL ."Rpc-Params: {$args};".PHP_EOL; socket_write($this->client, $proto); // 讀取服務(wù)端傳來的數(shù)據(jù) $buf = socket_read($this->client, 8024); socket_close($this->client); return $buf; } } $rpcClient = new RpcClient('http://127.0.0.1:8080/news'); echo $rpcClient->display(['title'=>'txl']); echo $rpcClient->display(['title'=>'hello world']);
服務(wù)類 News
<?php class News { public function display($data) { return json_encode(['result'=>"News display(), title is {$data['title']}"]); } }
運行測試:
Client
Server
到此這篇關(guān)于PHP創(chuàng)建簡單RPC服務(wù)案例詳解的文章就介紹到這了,更多相關(guān)PHP創(chuàng)建簡單RPC服務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
PHP實現(xiàn)動態(tài)執(zhí)行代碼的方法
這篇文章主要介紹了PHP實現(xiàn)動態(tài)執(zhí)行代碼的方法,涉及PHP中create_function函數(shù)的使用技巧,需要的朋友可以參考下2016-03-03