文档章节

PHP的rpc客户端和服务端

我爱吃炒鸡
 我爱吃炒鸡
发布于 07/16 18:46
字数 1503
阅读 25
收藏 0

[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;
    }
}

© 著作权归作者所有

我爱吃炒鸡

我爱吃炒鸡

粉丝 2
博文 139
码字总数 452111
作品 0
东城
高级程序员
私信 提问
php实现的一个简单json rpc框架实例

son rpc 是一种以json为消息格式的远程调用服务,它是一套允许运行在不同操作系统、不同环境的程序实现基于Internet过程调用的规范和一系列的实现。这种远程过程调用可以使用http作为传输协议...

mickelfeng
2016/08/07
258
0
基于 Swoole 的轻量级高性能框架 swoolefy 1.0.6 发布

swoolefy是基于swoole实现的轻量级高性能框架,框架支持http,websocket,udp服务器,以及基于tcp实现可扩展的rpc服务,同时支持composer包方式安装部署项目。基于实用,swoolefy抽象Event事...

bingcool
2018/06/11
1K
5
thinkpc/Dora-RPC

Dora RPC 简介(Introduction) Dora RPC 是一款基础于Swoole定长包头通讯协议的最精简的RPC, 用于复杂项目前后端分离,分离后项目都通过API工作可更好的跟踪、升级、维护及管理。 问题提交: I...

thinkpc
2017/03/10
0
0
php调用web services两种方法soap and xml-rpc

Web Service介绍 Web Service就是为了异构系统的通信而产生的,它基本的思想就是使用基于XML的HTTP的远程调用提供一种标准的机制,而省去建立一种新协议的需求。 目前进行Web Service通信有两...

Adam-Lee
2011/08/10
8.4K
1
PHP Thrift Rpc 远程调用框架 --workerman-thrift

workerman thrift rpc 是一个以workerman作为服务器容器,使用Thrift协议及其传输层模块搭建起来的跨语言的RPC远程调用框架。 workerman-thrift-rpc的目标是解决异构系统之间通信的问题,wor...

domr
2014/04/13
2.9K
1

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周日乱弹 —— 我,小小编辑,食人族酋长

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @宇辰OSC :分享娃娃的单曲《飘洋过海来看你》: #今日歌曲推荐# 《飘洋过海来看你》- 娃娃 手机党少年们想听歌,请使劲儿戳(这里) @宇辰OSC...

小小编辑
今天
681
10
MongoDB系列-- SpringBoot 中对 MongoDB 的 基本操作

SpringBoot 中对 MongoDB 的 基本操作 Database 库的创建 首先 在MongoDB 操作客户端 Robo 3T 中 创建数据库: 增加用户User: 创建 Collections 集合(类似mysql 中的 表): 后面我们大部分都...

TcWong
今天
39
0
spring cloud

一、从面试题入手 1.1、什么事微服务 1.2、微服务之间如何独立通讯的 1.3、springCloud和Dubbo有哪些区别 1.通信机制:DUbbo基于RPC远程过程调用;微服务cloud基于http restFUL API 1.4、spr...

榴莲黑芝麻糊
今天
25
0
Executor线程池原理与源码解读

线程池为线程生命周期的开销和资源不足问题提供了解决方 案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。 线程实现方式 Thread、Runnable、Callable //实现Runnable接口的...

小强的进阶之路
昨天
72
0
maven 环境隔离

解决问题 即 在 resource 文件夹下面 ,新增对应的资源配置文件夹,对应 开发,测试,生产的不同的配置内容 <resources> <resource> <directory>src/main/resources.${deplo......

之渊
昨天
71
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部