当前位置:首页>>PHP实现RPC(简版)

PHP实现RPC(简版)

  • 公共
  • 2022-07-15 17:06:43

概述

RPC这个东西是什么? 第一次听说他, 还要在它的前边加个G, 当时我以为GRPC是一项技术, 后来才知道, 并不是这样. GRPC只是RPC的谷歌实现.

谷歌搜了一下, RPC就是一种: 远程函数调用, 看到这里, 我已经等不及了, 不往下看了, 先自己实现一个. 如果只给你这样一个概念, 如何实现调用远程函数的功能呢?

自己实现

自己尝试实现一个粗糙的PHP版本. (不想看可以跳过的)

思路

远程调用, 只需要解决下面问题:

  1. 通信问题
  2. 定义传输的数据格式
  3. 如何封装后可以达到像调用本地函数一样的效果

先来解决通信问题, 直接粗暴的tcp socket

传输的数据格式, 直接用json进行传输

调用本地函数?? 这就要借助一下PHP的魔术函数了, __call() 这个函数是一个类调用不存在的方法时会跑到这里来, 所以, 我们返回一个类, 在call方法中进行远程调用, 这样, 在本地看来就只是在调用一个方法.

开始实现

PHP中进行socket连接十分简单, 直接调用系统函数. 通信问题解决了, 剩下的就是传输数据了, so easy

经过一番摸索, 看下结果

服务器内容:

<?php
class RpcServer{

    private $port = 0; // 监听端口号
    private $host = ''; // IP

    public function __construct($host, $port){
        $this->host = $host;
        $this->port = $port;
    }

    /**
     * 运行, 监听端口并处理
     */
    public function run(){
        // 创建socket
        $server = stream_socket_server("tcp://{$this->host}:{$this->port}");
        if(empty($server)) throw new Exception('创建套接字失败');
        // 监听
        while (true){
            $client = stream_socket_accept($server);
            if(empty($client)) continue;
            // 处理请求
            $this->disposeClient($client);
            fclose($client);
        }
    }

    private function disposeClient($client){
        $buf = fread($client, 4096);
        $array = json_decode($buf, true);
        // 创建对象并调用方法
        $class = $array['class'] ?? '';
        $method = $array['method'] ?? '';
        $params = $array['params'] ?? [];
        $instance = new $class();
        $result = $instance->$method(...$params);
        fwrite($client, json_encode($result));
    }
}
// 测试调用类
class Test{
    public function tt(){
        return 'return_tt';
    }

    public function add($a, $b){
        return $a + $b;
    }
}

(new RpcServer('127.0.0.1', 8888))
    ->run();

调用方:

<?php
class RpcClient{
    private $urlInfo = null;
    private $className = '';

    private function __construct($url, $className){
        $this->urlInfo = parse_url($url);
        $this->className = $className;
    }

    public static function getInstance($className){
        return new RpcClient('127.0.0.1:8888', $className);
    }

    public function __call($name, $arguments){
        // 创建客户端
        $client = stream_socket_client("tcp://{$this->urlInfo['host']}:{$this->urlInfo['port']}");
        if(empty($client)) return null;
        // 发送数据
        fwrite($client, json_encode([
            'class' => $this->className,
            'method' => $name,
            'params' => $arguments,
        ]));
        // 接收返回
        $data = fread($client, 4096);
        // 关闭客户端
        fclose($client);
        return json_decode($data, true);
    }
}

$test = RpcClient::getInstance('Test');
echo $test->tt(), PHP_EOL;
echo $test->add(4, 6);

结果:

PHP实现RPC(简版)

嗯, 还阔以. 当然, 问题还是有很多的, 比如不能实现保存对象的修改状态等等.

其实对象可以通过序列化和反序列化来传输, 额, Java中, 不知道PHP有没有这种技术.

当然, 一个RPC中必然大量使用反射序列化动态加载代理网络请求等等, 这只是一个超级超级粗糙的示例.

继续

nice, 自己做完了, 对RPC是个什么东西有了一个基本的概念.

WHAT

RPC是什么? 简单说, 就是远程函数调用. 字面意思, 很好理解.

WHY

看到一个技术, 一定会问的一个问题就是: 为什么? 一个技术基本不会平白无故出现, 都是为了解决某些问题, 那么RPC解决了什么问题呢? 字面含义: 远程函数调用

为什么要进行远程函数调用, 把函数拿过来本地调用不就好了? 还不用走网络IO, 速度更快一些. 很好, 现在假设, 你真的这样做了, 当项目变得庞大, 你想要进行拆分, 拆分后的有: 项目A, 项目B…, 这时, 你发现这些拆分的项目部分逻辑是重叠的, 比如用户信息相关, 怎么办? 如果不抽出来, 以后的维护成本会变得很高, 一处改处处改. 如果抽出来, 跨项目如何进行调用? 哎, 走过路过不要错过, RPC推荐给你.

HOW

那么如何实现RPC呢?

在刚才使用PHP简单实现中, 已经发现了. 需要解决的问题如下:

  1. 网络通信
  2. 信息格式
  3. 对象状态保存

1.网络通信

说到底, 网络通信不过两种: tcp udp.

有没有使用udp实现的RPC呢? 貌似也有.

使用tcp协议实现的RPC也有, 当然, 不光传输层协议, 也有直接通过应用层协议: httpwebsocket等等建立连接的. 当然, 如果需要频繁调用, 可以不断开tcp连接, 在一段时间内一直保持连接, 避免频繁握手.

2.信息格式

信息格式就有很多选择了, jsonxml等等, 也可以自己定制, 只要发送端和接收端统一信息格式就行了.

3.对象状态保存

对于一个类的调用, 通常都会有类状态修改的操作, 比如调用setName方法, 如何保存对象的信息呢? 当然, 可以服务端将对象在内存中的信息直接序列化发回去, 当客户端下次调用时携带序列化信息, 服务端接收后反序列化还原对象继续操作.

过程

个人理解的RPC调用过程:

  1. 客户端创建RPC对象
  2. 客户端调用方法
  3. RPC解析方法并将对象及参数做序列化
  4. RPC通过网络连接发送方法调用
  5. 服务端接收到方法调用, 解析对象及参数反序列化
  6. 服务端执行方法并将结果序列化返回
  7. 客户端接收到结果并进行解析, 返回给本地调用者
  8. 拿到最终结果

RPC适用于内部网络不同项目之间的通信, 如果是对外暴露的, 个人感觉还是通过接口的形式吧.

使用RPC显然会丧失一部分性能, 毕竟调用要走网络IO, 尽管是内网, 仍然要比本地调用慢上一些, 但带来了更好的可扩展性和可维护性, 感觉还是不错的.

之后如果用到的话, 拉个框架看看源码.

个人理解, 以上…

猜你喜欢