PHP的rpc客户端和服务端

[TOC]

PHP的rpc客户端和服务端

PHP的rpc有很多解决方案, composer上可以看得到很多开远的基于PHP代码的rpc框架.

也有很多基于C的PHP扩展的rpc框架,比如: yar,grpc.都很不错.

这里比较推荐的是grpc,grpc同时支持多种语言包括但不限于,go,php,c++, grpc官方php文档

现成框架的用起来都很简单, 直接安装调用即可,我就不说了.这里直说一下,使用PHP代码写的rpc服务.

swoole官方提供的rpc服务端和客户端代码

下面这个简单的案例很简单,就是这么个结构.

~/Desktop/rpcDemo ⌚ 18:45:43
$ tree
.
├── rpcClient.php
├── rpcServer.php
└── service
    └── test.php

1 directory, 3 files

rpc协议简单的说明

RPC(Remote Procedure Call)—远程过程调用

RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。

RPC框架与具体的协议无关。RPC 可基于 HTTP 或 TCP 协议,Web Service 就是基于 HTTP 协议的 RPC,它具有良好的跨平台性,但其性能却不如基于 TCP 协议的 RPC。

用的最多的时,各种虚拟货币钱包中的调用都是通过RPC来实现的.

简而言之, 一般RPC调用都用在跨语言调用或者分部署调用中.

比较简单的方式是数据包中每一行一个参数,冒号前为字段名,冒号后为值.

rpc-module:moduleName;\n
rpc-class:className;\n
rpc-action:className;\n
rpc-params:paramsData;\n

比如要调用https://www.baidu.com/hospital/putian?patient=liyanhong&disease=eatbloodbread

但是,如果百度用其他语言写的,或者说为了便于调用,不想用一般的那种resetApi接口的方式拿接口返回值.

假定百度的服务是用PHP写的,没有做路由.那他的文件路径应该是: /index/hospital/putian/传递了两个参数是:patient=liyanhong和disease=eatbloodbread

这时候就要用RPC了,两边可以协商通信方式和参数传递的方式,那他的传参方式应该是这样的.

rpc-module:index;\n
rpc-class:hospital;\n
rpc-action:putian;\n
rpc-params:{"patient":"liyanhong","isease":"eatbloodbread"};\n

简单的纯PHP代码的rpc服务端

<?php

/**
 * rpc服务
 */
class rpcServer
{
    protected $server;

    /**
     * 返回数据 function.
     *
     * @param [type] $data
     *
     * @return mixed
     */
    public function response($data)
    {
        if (is_array($data)) {
            $data = json_encode($data);
        }
        echo $data.PHP_EOL;

        return;
    }

    /**
     * 创建服务 function.
     *
     * @param [string] $host 链接
     * @param [int]    $port 端口号
     */
    public function instance($host, $port)
    {
        //创建一个 Socket 服务
        //AF_INET IPv4 网络协议
        //SOCK_STREAM 提供一个顺序化的、可靠的、全双工的、基于连接的字节流。支持数据传送流量控制机制。TCP 协议即基于这种流式套接字。
        //SOL_TCP   TCP和UDP对应SOL_TCP 和 SOL_UDP
        if (($this->server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
            $this->response('socket_create() 执行失败:'.socket_strerror(socket_last_error()));
        }

        //设置端口重用
        if (!socket_set_option($this->server, SOL_SOCKET, SO_REUSEADDR, 1)) {
            $this->response('socket_set_option() 执行失败:'.socket_strerror(socket_last_error()));
        }
        //绑定端口
        if (socket_bind($this->server, $host, $port) < 0) {
            $this->response('socket_set_option() 执行失败:'.socket_strerror(socket_last_error()));
        }
        //监听端口
        if ((socket_listen($this->server, 3)) < 0) {
            $this->response('socket_listen() 执行失败:'.socket_strerror(socket_last_error()));
        }
    }

    /**
     * 构造函数 function.
     *
     * @param [string] $host 链接
     * @param [int]    $port 端口号
     * @param [string] $path 请求的方法路径
     */
    public function __construct($host, $port, $path)
    {
        // 判断 RPC 服务目录是否存在
        $realPath = realpath(__DIR__.$path);
        if (!is_dir($realPath)) {
            $this->response('path参数错误,目录不存在:'.$path);
        }
        //激活服务端
        $this->instance($host, $port);
        //执行操作
        $this->processing($realPath);
    }

    /**
     * processing function.
     *
     * @param [string] $realPath 服务代码路径
     */
    public function processing($realPath)
    {
        do {
            //开始执行操作
            $client = socket_accept($this->server);
            if ($client) {
                // 一次性读取
                $buffer = socket_read($client, 1024);
                echo '接收到的客户端1024长度内的数据: '.PHP_EOL.$buffer.PHP_EOL;

                //正则判断客户端提交来的数据
                $classData = preg_match('/Rpc-Class:\s(.*);\n/i', $buffer, $class);
                $methodData = preg_match('/Rpc-Method:\s(.*);\n/i', $buffer, $method);
                $paramsRet = preg_match('/Rpc-Params:\s(.*);\n/i', $buffer, $params);
                if ($classData && $methodData) {
                    $class = $class[1];
                    $method = $method[1];
                    $params = $params[1];
                    if (!empty($params)) {
                        $params = json_decode($params, true);
                        if (is_array($params)) {
                            $params = implode(',', $params);
                        }
                    }

                    $file = $realPath.'/'.$class.'.php';  // 类文件需要和类名一致
                    $data = ''; // 执行结果,作为返回值
                    // 判断类文件是否存在
                    if (file_exists($file)) {
                        // 引入类文件
                        include $file;
                        // 实例化类, ReflectionClass它是用来导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。
                        $refObj = new ReflectionClass($class);
                        // 判断指定方法是否存在该对象中
                        if ($refObj->hasMethod($method)) {
                            // 执行该对象方法
                            $refMethod = $refObj->getMethod($method);
                            if (!empty($params)) {
                                //为方法传递参数
                                $data = $refMethod->invokeArgs($refObj->newInstance(), [$params]);
                            }
                        } else {
                            socket_write($client, '方法不存在');
                        }
                        //把运行后的结果返回给客户端
                        socket_write($client, $data);
                    }
                } else {
                    socket_write($client, '对象或方法不存在');
                }

                // 关闭客户端
                socket_close($client);
            }
        } while (true);
    }

    /**
     * 析构函数 function.
     */
    public function __destruct()
    {
        socket_close($this->server);
    }
}
//这里Mac的直接用这端口最好
new rpcServer('127.0.0.1', 8080, '/service');

简单的纯PHP代码的rpc客户端

<?php

/**
 * rpc客户端类.
 */
class rpcClient
{

    protected $client = null;
    protected $urlInfo = [];   // 远程调用 URL 组成部分

    /**
     * rpcClient constructor.
     *
     * @param $url
     */
    public function __construct($url)
    {

        // 解析 URL
        $this->urlInfo = parse_url($url);
    }

    /**
     * Method  __call
     *
     * @desc    ......
     *
     * @param $name
     * @param $arguments
     *
     * @return  void
     */
    public function __call($name, $arguments)
    {
        $socketHandler = fsockopen($this->urlInfo['host'], $this->urlInfo['port']);
        // 传递调用的类名
        $class = basename($this->urlInfo['path']);
        // 传递调用的参数
        $args = '';
        if (isset($arguments[0])) {
            $args = json_encode($arguments[0]);
        }
        // 向服务端发送我们自定义的协议数据
        $data = "Rpc-Class: {$class};" . PHP_EOL
            . "Rpc-Method: {$name};" . PHP_EOL
            . "Rpc-Params: {$args};" . PHP_EOL;
        fputs($socketHandler, $data);
        $start_time   = time();
        $responseData = '';
        while (!feof($socketHandler)) {
            $responseData .= fread($socketHandler, 1024);
            $diff         = time() - $start_time;
            if ($diff > 24) {
                die('Timeout!n');
            }
            $status = stream_get_meta_data($socketHandler);
            if ($status['timed_out']) {
                $this->response('Stream Timeout!n');
            }
        }
        var_dump($responseData);
        fclose($socketHandler);
    }

    /**
     * 返回数据 function.
     *
     * @param [type] $data
     *
     * @return mixed
     */
    public function response($data)
    {
        if (is_array($data)) {
            $data = json_encode($data);
        }
        echo $data . PHP_EOL;

        return;
    }
}

//这里Mac的直接用这端口最好
$rpcClient = new RpcClient('http://127.0.0.1:8080/test');
echo $rpcClient->demo1(['title' => '标题', 'content' => '内容']);

service目录下的文件的test.php

<?php

class test
{
    public function __construct()
    {
    }

    public function demo1($data = '')
    {
        return '这是test::demo1 返回的请求参数: '.$data;
    }

    public function demo2($data = '')
    {
        return '这是test::demo2 返回的请求参数: '.$data;
    }
}

转载于:https://my.oschina.net/chinaliuhan/blog/3074943

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
评论
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值