gRPC基本使用

· Read in about 3 min · (467 Words)
dev work

通过RPC,客户端的应用程序可以方便地调用另外一台机器上的服务端程序,就像调用本地函数一样。

安装gRPC

标准安装可参考gRPC Install

这里摘录不同语言安装gRPC时的操作

C++          从源代码编译
C#           NuGet包Grpc
Go           go get google.golang.org/grpc
Node         npm install grpc
PHP          pecl install grpc
Python       pip install grpcio
Ruby         gem install grpc
Java         参考 github.com/grpc/grpc-java
Objective-C  参考 github.com/grpc/grpc/tree/master/src/objective-c

默认gRPC使用的是Protocol buffers,所以默认还需要安装protobuf

如果是Mac操作系统,可以这样同时安装gRPC系列

brew tap grpc/grpc
brew install --with-plugins grpc

这样安装的gRPC会同时安装protocgrpc_cpp_plugin, grpc_node_plugin, grpc_php_plugin, grpc_python_plugin, grpc_csharp_plugin, grpc_objective_c_plugin, grpc_ruby_plugin这些可执行文件。

编写protobuf文件

由于gRPC默认使用protobuf作为传输格式,所以这里需要先编写相应proto文件。示例如下:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.hello";
option java_outer_classname = "HelloProto";
option objc_class_prefix = "HL";

package hello;

// The greeting service definition.
service Greeter {
    // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
    string name = 1;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
}

这里使用的是proto3语法,详细规则请参看Protobuf项目。

编译成相应源文件

不同的语言进行编译的时候,所使用的参数大同小异,但是略有差别(特别是生成的文件),使用过程中需要留意。

各语言编译(注意此处的grpc_xxx_plugin是上面安装的,如果没有安装或者是其它系统,请自行先安装)

# 假设上面的proto文件保存在当前目录的protos目录下,文件名为hello.proto

# Python; 生成文件 ./hello/hello_pb2.py
protoc -I protos --plugin=grpc_python_plugin --python_out=./hello ./protos/*.proto

# PHP; 生成目录./hello/Hello/ 与./hello/GPBMetadata/,目录下有相关文件
protoc -I protos --plugin=grpc_php_plugin --php_out=./hello ./protos/*.proto

# Node.js; 生成文件 ./hello/hello_grpc_pb.js与./hello/hello_pb.js
protoc -I protos --js_out=import_style=commonjs,binary:./hello/ --grpc_out=./hello --plugin=protoc-gen-grpc=$(which grpc_node_plugin) ./protos/*.proto

# Java
#protoc --plugin=protoc-gen-grpc-java=/path/to/protoc-gen-grpc-java --grpc-java_out=$DST_DIR --proto_path=$SRC_DIR $SRC_DIR/jr.proto

编译Go代码

protoc -I protos --go_out=plugins=grpc:./hello ./protos/*.proto

# -I            导入路径,默认是当前文件夹,使用-I参数可以去掉生成代码目录树继承的问题
# --go_out      编译成go代码时输出

注意 在gRPC的编译时,Java与Go的编译与其它语言稍有区别,Java使用可参考gRPC 初探

启动gRPC服务端

这里以Go语言为例,上面proto文件生成的Go语言代码hello.pb.go文件, 其文件内容与gRPC helloworld.pb.go这个文件类似(唯一不同之处是包名由hello变成了helloworld)。

这里在项目根目录 trpc 下创建一个main.go文件

package main

import (
	"fmt"
	"net"

	pb "trpc/hello"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

type server struct{}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (re *pb.HelloReply, err error) {
	fmt.Println("gRPC called!")
	re = &pb.HelloReply{Message: "Hello, " + in.Name}
	return
}

func main() {
	ln, err := net.Listen("tcp", ":8081")
	if err != nil {
		panic(err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	s.Serve(ln)
}

将项目编译后运行,其监听在8081端口。

运行gRPC客户端

这里以Node.js客户端为例,Node的客户端调用有两种方式:动态引用与静态生成。

首先新建一个项目,安装gRPC,然后

mkdir noderpc
cd noderpc
npm init
npm i grpc --save
  • 动态引用(动态引用方式无须先编译proto文件)

    const grpc = require('grpc');
    
    const protoPath = './protos/hello.proto';
    let loader = grpc.load(protoPath);
    let helloProto = loader.hello;
    
    function greet() {
      let client = new helloProto.Greeter('localhost:8081', grpc.credentials.createInsecure());
      client.sayHello({name: 'beijing'}, (err, resp) => {
        if (err) {
          console.error(err);
        } else {
          console.log(resp.message);
        }
      });
    }
    greet();
    
  • 静态编译方式

    静态编译需要先将proto文件编译成javascript文件。

    protoc -I protos --js_out=import_style=commonjs,binary:./lib/ --grpc_out=./lib --plugin=protoc-gen-grpc=$(which grpc_node_plugin) ./protos/hello.proto
    

    然后在代码中引用这两个文件

    const grpc = require('grpc');
    const svc = require('./lib/hello_grpc_pb.js');
    const msg = require('./lib/hello_pb.js');
    
    function greet() {
      let req = new msg.HelloRequest();
      req.setName("beijin");
    
      let client = new svc.GreeterClient('localhost:8041', grpc.credentials.createInsecure());
      client.sayHello(req, (err, resp) => {
        if (err) {
          console.error(err);
        } else {
          console.log(resp.getMessage());
        }
      });
    
    }
    greet();
    

使用SSL进行通信

这里以Go语言为例:

服务端代码:

var (
    cert = "certs/server.crt"
    key  = "certs/server.key"
)
creds, err := credentials.NewServerTLSFromFile(cert, key)
if err != nil {
    return fmt.Errorf("could not load TLS keys: %s", err)
}
server := grpc.NewServer(grpc.Creds(creds))

客户端代码:

var cert = "server.crt"
creds, err := credentials.NewClientTLSFromFile(cert, "")
if err != nil {
    return fmt.Errorf("could not load tls cert: %s", err)
}
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds))
if err != nil {
    return fmt.Errorf("could not dial %s: %s", addr, err)
}
client := pb.NewSecureClient(conn) // 调用自定义 protobuf 函数

更多更详细的示例请阅读gRPC examples

引用资料

  1. gRPC初体验
  2. gRPC Node.js
  3. gRPC介绍与安装
  4. gRPC golang Authentication
  5. self-signed SSL gRPC
  6. secure gRPC
  7. Protocol Buffers 3 语言指南

Comments