Thrift RPC 通信搭建

一、先安装 Thrift 编译工具

MAC 电脑安装执行以下命令

    brew install thrift

Linux 环境下安装参考官网

官网地址:http://thrift.apache.org/download

安装完成之后

    $ thrift --version
    Thrift version 0.10.0

有以上输出说明工具安装成功

二、定义自己的Thrift 返回参数结构体

我这里定义了一个 thriftCommon.thrift 文件 这里我以 lumen框架为例 存放目录为thriftSource/thriftCommon.thrift 内容如下:

namespace php app.Library.Thrift  # 指定生成什么语言,生成文件存放的目录

// 返回结构体
struct Response {
    1: i32 code;    // 返回状态码
    2: string msg;  // 码字回提示语名
    3: string data; // 返回内容
}

// 服务体
service ThriftCommonCallService {
    // json 字符串参数  客户端请求方法
    Response invokeMethod(1:string params)
}

定义好以上文件之后使用第一步安装好的 thrift 工具来生成相应的文件

$ thrift -r --out ./ --gen php thriftSource/thriftCommon.thrift

执行该命令会在app/Library/Thrift目录生成ThriftCommonCallService.phpTypes.php文件,这是客户端文件可以分两个项目部署,也可以在同一个项目部署,我这边因为工作需要独立部署。

三、创建一个新的客户端项目

执行以下创建命令

    $ composer create-project laravel/lumen server --prefer-dist "5.5"

把第二步生成的客户端的两个文件拷贝到 app/Library/Thrift目录 如图:

file

四、服务端的实现

安装第三方使用包

$ composer require apache/thrift

使用 thrift 命令生成服务端代码

    $ thrift -r --out ./ --gen php:server thriftSource/thriftCommon.thrift

执行该命令会在app/Library/Thrift目录生成ThriftCommonCallService.phpTypes.php文件 打开 ThriftCommonCallService.php文件我们需要实现里面的接口ThriftCommonCallServiceIf 接下来我们建立实现文件 app/Library/Rpc/Server.php

<?php
namespace App\Library\Rpc;

use App\Library\Thrift\ThriftCommonCallServiceIf;
use App\Library\Thrift\Response;

class Server implements ThriftCommonCallServiceIf
{
    /**
     * 实现 socket 各个service 之间的转发
     * @param string $params
     * @return Response
     * @throws \Exception
     */
    public function invokeMethod($params)
    {
         // 转换字符串 json
         $input = json_decode($params, true);
         // 自己可以实现转发的业务逻辑
        ...

        $response = new Response();
        $response->code = 200;
        $response->msg = '';
        $response->data = json_encode($input);
        return $response;
    }

建立控制器实现服务端 端口启动 app/Http/Controllers/SocketController.php

<?php
namespace App\Http\Controllers;

use App\Library\Rpc\Server;
use app\Library\Thrift\Response;
use app\Library\Thrift\ThriftCommonCallServiceProcessor;
use Illuminate\Http\Request;
use Thrift\Exception\TException;
use Thrift\Factory\TBinaryProtocolFactory;
use Thrift\Factory\TTransportFactory;
use Thrift\Server\TServerSocket;
use Thrift\Server\TSimpleServer;
use Thrift\TMultiplexedProcessor;

class SocketController extends Controller
{
    /**
     * 启动 socket 连接
     */
    public function server()
    {
        try {
            $thriftProcessor = new ThriftCommonCallServiceProcessor(new Server());
            $tFactory = new TTransportFactory();
            $pFactory = new TBinaryProtocolFactory(true, true);
            $processor = new TMultiplexedProcessor();
            // 注册服务
            $processor->registerProcessor('thriftCommonCallService', $thriftProcessor);

            // 监听开始
            $transport = new TServerSocket('127.0.0.1', 9999);
            $server = new TSimpleServer($processor, $transport, $tFactory, $tFactory, $pFactory, $pFactory);
            $server->serve();
        } catch (TException $te) {
            app('log')->error('socket 服务启动失败', ['content' => $te->getMessage()]);
        }
    }

建立访问路由或php artisan启动命令

// 访问路由
$router->get('/rpc/server', 'SocketController@server');

建立php artisan文件 目录 app/Console/Commands/RpcServer.php

 <?php
/**
 * Created by PhpStorm.
 */

namespace App\Console\Commands;

use App\Http\Controllers\SocketController;
use Illuminate\Console\Command;

class RpcServer extends Command
{
    /**
     * 控制台命令 signature 的名称。
     *
     * @var string
     */
    protected $signature = 'server:rpc';

    /**
     * 控制台命令说明。
     *
     * @var string
     */
    protected $description = 'rpc 服务';

    protected static $socketController;

    /**
     * rpcServer constructor.
     * @param SocketController $socketController
     */
    public function __construct(SocketController $socketController)
    {
        parent::__construct();
        self::$socketController = $socketController;
    }

    /**
     * 执行控制台命令。
     *
     * @return mixed
     */
    public function handle()
    {
        self::$socketController->server();
    }
}

app/Console/Kernel.php注册

<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Laravel\Lumen\Console\Kernel as ConsoleKernel;
use App\Console\Commands\RpcServer;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        RpcServer::class
    ];

到此服务端实现已全部完成 接下来实现客户端并成功与服务端通信

五、客户端的实现 (以第三步创建的项目为例继续说明)

安装第三方使用包

$ composer require apache/thrift

建立文件 app/Library/Tools/Socket.php

<?php
namespace App\Library\Tools;

use app\Library\Thrift\ThriftCommonCallServiceClient;
use Thrift\Protocol\TBinaryProtocol;
use Thrift\Protocol\TMultiplexedProtocol;
use Thrift\Transport\TBufferedTransport;
use Thrift\Transport\TSocket;

class Socket
{
    // 保存对象实例化
    private $_instance;

    // 保存服务连接池
    private static $client = [];

    // 配置文件
    private $config = [];

    private function __construct($type)
    {
        $config = [
            'erp' => [
                'host' => env('ERP_RPC_HOST'),
                'port' => env('ERP_RPC_PORT')
            ]
        ];
        $this->config = $config[$type];
    }

    /**
     * 连接服务
     * @param string $name  调用的方法名
     * @param array $args   1、参数数组 2、具体哪个方法名  3、所属的 Service 名称
     * @return bool
     */
    public static function __callStatic($name, $args)
    {
        if (substr($name, 0, 8) != 'SocketTo') {
            return false;
        }

        $type = strtolower(substr($name, 8));
        // 实例化操作
        if (!isset(self::$client[$type])) {
            self::$client[$type] = new self($type);
        }

        return self::$client[$type]->invoke($args);
    }

    private function invoke($args)
    {
        try {
            $socket = new TSocket($this->config['host'], $this->config['port']);
            $socket->setRecvTimeout(50000);
            $socket->setDebug(true);
            $transport = new TBufferedTransport($socket, 1024, 1024);
            $protocol = new TBinaryProtocol($transport);
            $thriftProtocol = new TMultiplexedProtocol($protocol, 'thriftCommonCallService');
            $client = new ThriftCommonCallServiceClient($thriftProtocol);
            $transport->open();
            // 拼装参数与类型
            $data = [
                'params' => $args[0],
                'methodName' => $args[1],
                'serviceName' => $args[2]
            ];
            $result = $client->invokeMethod(json_encode($data));
            $result->data = json_decode($result->data, true);
            $transport->close();
            return $result;
        } catch (\TException $Te) {
            app('log')->error('服务连接失败 ', ['host' => $this->config, 'methodName' => $args[1], 'content' => $Te->getMessage()]);
            return ['host' => $this->config, 'methodName' => $args[1], 'content' => $Te->getMessage()];
        }
    }
}

建立控制器来调用 并配置路由方问

<?php
namespace App\Http\Controllers;

use App\Library\Tools\Socket;
use Illuminate\Http\Request;

class ClientController extends Controller
{
    public function client(Request $request)
    {
       return ['code' => 20000, 'data' => ['name' => 'admin', 'roles' => ['admin']]];
       //接收参数
       $param = $request->input("params");
       //调用Service
        $data = Socket::SocketToErp($param, 'login', 'LoginService');
        return response()->json($data);
    }

以上就是实现客户端与服务端通信的内容,如有不懂可留言一块讨论^_^

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 5年前 自动加精
994914376
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 14

1.thrift生成的php文件,里面包含了好几个类,建议按照psr-4拆分成独立文件
2.如果有多个服务要注册,最好把注册封装起来,实现根据系统已有的服务批量注册,然后启动监听,这样就一个控制器或者不用控制器了(在系统初始化阶段实现注册和监听)

5年前 评论
994914376

@tsin 这个服务都是独立的,只需客户端监听不同的服务仓库就行,现在服务端已经是不需要控制器了,只需争对业务编写即可

5年前 评论

thrift -r --out ./ --gen php:server,psr4 thriftSource/thriftCommon.thrift
加上server和psr4选项,生成服务端处理器,同时按照psr4标标准生成文件。

5年前 评论

自己应该可以用swoole做一个简单的rpc服务吧

5年前 评论

@994914376 服务端怎么设置以TFramedTransport方式传输呢?看代码意思是调用静态方法
TTransportFactory::getTransport(new TFramedTransport());
但是怎么都调不通!

客户端以TFramedTransport方式成功调通,跟Python交互式没问题的!

file

file

5年前 评论

Symfony\Component\Debug\Exception\FatalThrowableError : Class 'app\Library\Thrift\ThriftCommonCallServiceProcessor' not found

5年前 评论

'app\Library\Thrift\ThriftCommonCallServiceProcessor' not found 怎么解决@994914376

5年前 评论

TSocket read 0 bytes

@994914376 这个是什么问题

5年前 评论
994914376

@jokerzhang 服务端报错了,单独把服务端运行起来这个错误就没了

5年前 评论

@jokerzhang 'app\Library\Thrift\ThriftCommonCallServiceProcessor' not found 这个问题你是怎么解决?

5年前 评论

'app\Library\Thrift\ThriftCommonCallServiceProcessor' not found 怎么解决? @994914376

5年前 评论

@Icharle 把app全部改成App就好

5年前 评论

如果客户端也是常驻内存的, 能不能给个客户端连接池的解决方案

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!