PHP & Golang 关于Grpc 学习笔记

Grpc 入门介绍

gRPC 官方文档中文版_V1.0

安装教程

在Golang 下安装
go get google.golang.org/grpc v1.56.1 
go get google.golang.org/protobuf v1.30.0 
在PHP下安装grpc 扩展
  1. 使用composer 安装grpc和protobuf(推荐使用)。

    {
      "name": "root/work",
      "type": "composer-plugin",
      "autoload": {
        "psr-4": {
          "Root\\Work\\": "src/"
        }
      },
      "authors": [
        {
          "name": "wangxiaoyang"
        }
      ],
      "require": {
        "grpc/grpc": "^1.52",
        "google/protobuf": "^3.23"
      }
    }

  2. 安装php扩展(grpc.so,protobuf.so)

    pecl install grpc
    pecl install protobuf

  3. grpc_php_plugin 依赖安装(安装过程较慢)(用于生成客户端代码,可手动写客户端代码)

    git clone -b RELEASE_TAG_HERE https://github.com/grpc/grpc
    cd grpc
    git submodule update --init
    mkdir -p cmake/build
    cd cmake/build
    cmake ../..
    make protoc grpc_php_plugin

安装protobuf 代码编辑器

  1. Mac 下推荐使用 brew 安装。安装命令

    brew install protobuf

  2. Windows 下参考:https://zhuanlan.zhihu.com/p/361997082

  3. 安装完成后通过命令: protoc 检查是否成功

     

  4. 关于对protoc 的使用说明

    1. 在Golang 下编译命令

      protoc --proto_path=. --go_out=. proto/greeter/greeter.proto 
    2. PHP下编译命令

      protoc --proto_path=. --php_out=. proto/greeter/greeter.proto 
  5. 搜索参数路径

    1. 即 -IPATH,--proto_path=PATH ,表示要在哪个文件路径下搜索.proto 文件,既可以用-I 指定,也可以通过--proto_path= 指定路径。

    2. 当不指定路径时,默认当前路径, 也可以指定多个路径,在多个路径下搜索。

    3. 如下命令。含义相同

      protoc --go_out=. proto/greeter/greeter.proto
      
      #点号表示当前路径,注意-I参数没有等于号
      protoc -I. --go_out=. proto/greeter/greeter.proto 
      
      protoc --proto_path=. --go_out=. proto/greeter/greeter.proto 

  6. 语言插件路径

    1. 如: --cpp_out=, php_out= 等,若protoc 内置语言,则无需另外安装。内置语言如下:

     

  7. 若使用没有内置的语言,则需要单独安装插件。如:golang 的--go_out= , 对应的插件为:protoc-gen-go

    1. 安装命令如下

      # 最新版
      go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
      
      # 指定版本
      go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.3.0

  8. proto文件位置参数

    1. 即@<filename> 参数,指定了.proto 的文件具体位置, 如:

              /Users/yancy/project/go/src/grpc-client/hello/hello.proto

  9. Protobuf 语法规范

    https://www.topgoer.com/%E5%BE%AE%E6%9C%8D%E5%8A%A1/Protobuf%E8%AF%AD%E6%B3%95.html

    1. 见资料:Protobuf语法 · Go语言中文文档

Demo 

  1. 编写.proto 文件

    syntax = "proto3"; // 指定proto版本
    
    package hello;     // 指定默认包名// 指定golang包名
    option go_package = "./hello";// 定义Hello服务
    service Hello {
      // 定义SayHello方法
      rpc SayHello(HelloRequest) returns (HelloResponse) {}
    }
    // HelloRequest 请求结构
    message HelloRequest {
      string name = 1;
    }
    // HelloResponse 响应结构
    message HelloResponse {
      string message = 1;
      int64 code = 2;
    }

  2. 在golang下生成go文件

    protoc --go_out=./ --go_opt=paths=source_relative --go-grpc_out=./ --go-grpc_opt=paths=source_relative hello.proto

  3. 生成文件路径

  4. 编写gRpc服务端代码

    // Package server /**
    package main
    
    import (
       "context"
       "fmt"
       "google.golang.org/grpc"
       pb "grpc-client/hello"
       "log"
       "net"
    )
    
    const Port = ":50051"
    
    type server struct {
       pb.UnimplementedHelloServer
    }
    
    func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
       log.Printf("Received: %v\n", in.GetName())
       return &pb.HelloResponse{Message: "Hello, " + in.GetName()}, nil
    }
    
    func main() {
       listen, err := net.Listen("tcp", Port)
    
       if err != nil {
          log.Fatalf("failed to listen: %v", err)
       }
       s := grpc.NewServer()
       pb.RegisterHelloServer(s, &server{})
       fmt.Printf("Starting listen on port: %s", Port)
       if err := s.Serve(listen); err != nil {
          log.Fatalf("failed to serve: %v", err)
       }
    }

  5. 编写客户端代码

    1. Golang 客户端代码

      // Package client /**
      package main
      
      import (
         "golang.org/x/net/context"
         "google.golang.org/grpc"
         "google.golang.org/grpc/credentials/insecure"
         "google.golang.org/grpc/grpclog"
         pb "grpc-client/hello"
         "log"
      )
      
      const (
         Addr = "127.0.0.1:50051"
      )
      
      func main() {
         conn, err := grpc.Dial(Addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
         if err != nil {
            log.Fatalf("could not greet: %v", err)
         }
         defer conn.Close()
      
         // 初始化客户端
         c := pb.NewHelloClient(conn)
         // 调用方法
         req := &pb.HelloRequest{Name: "gRPC"}
         res, err := c.SayHello(context.Background(), req)
      
         if err != nil {
            grpclog.Fatalln(err)
         }
         log.Printf("Greeting: %s", res.GetMessage())
      }

    2. PHP 客户端代码

      <?php
      
      namespace Grpc\Client;
      
      use Grpc\BaseStub;
      use Grpc\Hello\HelloRequest;
      use Grpc\UnaryCall;
      
      class HelloClient extends BaseStub
      {
          /**
           * @throws \Exception
           */
          public function __construct($hostname, $opts, $channel = null)
          {
              parent::__construct($hostname, $opts, $channel);
          }
      
      
          public function sayHello(HelloRequest $argument, $metadata = [], $options = []): UnaryCall
          {
              return $this->_simpleRequest('Grpc.Hello.Hello/SayHello',
                  $argument,
                  ['Grpc\Hello\HelloResponse', 'decode'],
                  $metadata, $options);
          }
      
      }

  6. 测试

    1. PHP客户端接口测试代码

      public function helloRpcAction()
      {
      
          $hostname = "127.0.0.1:50051";
          try {
              $client = new HelloClient($hostname, [
                  'credentials' => ChannelCredentials::createInsecure(),
              ]);
              $request = new HelloRequest();
      
              $request->setName("phalcon");
      
              list($response, $status) = $client->SayHello($request)->wait();
              if ($status->code != STATUS_OK) {
                  ErrorHandler::setErrorInfo($status->code, $status->details);
                  return FALSE;
              }
              var_dump($response->getMessage());
              var_dump($response->getCode());
              var_dump($response->getData());
              die;
          } catch (\Exception $exception) {
              var_dump($exception->getMessage());
          }
      }

    2. 启用服务端

    3. 客户端调用

      1. golang客户端调用:

      2. php客户端调用: