PHP創(chuàng)建簡(jiǎn)單RPC服務(wù)案例詳解
RPC 定義
RPC(Remote Procedure Call)即遠(yuǎn)程過(guò)程調(diào)用,指被調(diào)用方法的具體實(shí)現(xiàn)不在程序運(yùn)行本地,而是在別的某個(gè)地方。主要應(yīng)用于不同的系統(tǒng)之間的遠(yuǎn)程通信和相互調(diào)用。
如 A 調(diào)用 B 提供的 remoteAdd 方法:
- 首先A與B之間建立一個(gè)TCP連接;
- 然后A把需要調(diào)用的方法名(這里是remoteAdd)以及方法參數(shù)(10, 20)序列化成字節(jié)流發(fā)送出去;
- B接受A發(fā)送過(guò)來(lái)的字節(jié)流,然后反序列化得到目標(biāo)方法名,方法參數(shù),接著執(zhí)行相應(yīng)的方法調(diào)用(可能是localAdd)并把結(jié)果30返回;
- A接受遠(yuǎn)程調(diào)用結(jié)果
有些遠(yuǎn)程調(diào)用選擇比較底層的 socket 協(xié)議,有些遠(yuǎn)程調(diào)用選擇比較上層的 HTTP 協(xié)議。
遠(yuǎn)程調(diào)用的好處:
- 解耦:當(dāng)方法提供者需要對(duì)方法內(nèi)實(shí)現(xiàn)修改時(shí),調(diào)用者完全感知不到,不用做任何變更;這種方式在跨部門(mén),跨公司合作的時(shí)候經(jīng)常用到,并且方法的提供者我們通常稱為:服務(wù)的暴露方
這里使用 PHP Socket 來(lái)創(chuàng)建一個(gè)服務(wù)端和客戶端,目錄結(jié)構(gòu)如下:
服務(wù)端
<?php class RpcServer { protected $server = null; public function __construct($host, $port, $path) { // 創(chuàng)建一個(gè) 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ā)送過(guò)來(lái)的協(xié)議 $classRet = preg_match('/Rpc-Class:s(.*);rn/i', $buf, $class); $methodRet = preg_match('/Rpc-Method:s(.*);rn/i', $buf, $method); $paramsRet = preg_match('/Rpc-Params:s(.*);rn/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; // 實(shí)例化類 $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'); } //把運(yùn)行后的結(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 = []; // 遠(yuǎn)程調(diào)用 URL 組成部分 public function __construct($url) { // 解析 URL $this->url_info = parse_url($url); } public function __call($name, $arguments) { // 創(chuàng)建一個(gè)客戶端 $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ù)端傳來(lái)的數(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']}"]); } }
運(yùn)行測(cè)試:
Client
Server
到此這篇關(guān)于PHP創(chuàng)建簡(jiǎn)單RPC服務(wù)案例詳解的文章就介紹到這了,更多相關(guān)PHP創(chuàng)建簡(jiǎn)單RPC服務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
版權(quán)聲明:
本站所有文章和圖片均來(lái)自用戶分享和網(wǎng)絡(luò)收集,文章和圖片版權(quán)歸原作者及原出處所有,僅供學(xué)習(xí)與參考,請(qǐng)勿用于商業(yè)用途,如果損害了您的權(quán)利,請(qǐng)聯(lián)系網(wǎng)站客服處理。