chenlong-io/blog

gRPC的简单使用

chenlong-io opened this issue · 0 comments

gRPC 是谷歌开源的一套多语言 RPC 框架,用官网的一句话来概括就是:

A high-performance, open-source universal RPC framework

翻译过来就是:一个高性能、开源通用的 RPC 框架。gRPC 也是一个遵循server/client模型的框架

gRPC 使用

先安装 grpc 和 @grpc/proto-loader

yarn add grpc @grpc/proto-loader

由于 gRPC 使用谷歌特有的 Protocol Buffer,用于序列化结构化数据的自动化过程,只需要定义如何组织你的结构化数据一次,就可以使用 protoc 轻松的根据这个定义生成语言相关的源代码(支持多种语言),以便于读写结构化数据。

这里通过 @grpc/proto-loader 来解析 proto 文件

关于 protoc buffer ,可以参考 Protocol Buffer是什么? 这篇文章

定义 hello.proto

protoBuffer 目前有 2 和 3 两个版本,这里我们用版本 3

// 使用 proto3
syntax = "proto3";

// 定义包名
package helloworld;

// 定义Hello服务
service Hello {
    // 定义 sayHello 方法
    rpc sayHello (HelloReq) returns (HelloRes);
}

// 定义 sayHello 方法的传值
message HelloReq {
    // 传入 name,1表示第一个参数
    string name = 1;
    // 传入 age,2表示第二个参数
    int32 age = 2;
    // 传入 job
    string job = 3;
}

message HelloRes {
    string message = 1;
}

protobuf 书写较为严格,不要忘记分号。

需要注意的是,以 string name = 1;为例,这里的 1 表示第一个参数,其实在JSON格式里表示第一个 key ,比如:

message HelloReq {
    // 传入 name,1表示第一个参数
    string name = 1;
    // 传入 age,2表示第二个参数
    int32 age = 2;
}

// 对应的 JSON, 第一个 key 必须是 name ,第二个是 age
{
	name: '',
	age: 20
}

Server

RPC 分为 Server 端和 Client 端,我们先写 Server 端代码

首先就是引入 grpc 和 @grpc/proto-loader,先使用 proto-loader 加载 proto 文件生成对应的 package,然后通过 grpc 加载这个包,并通过.helloworld来返回 helloword 这个包(对应的是 proto 文件里的 package helloworld;

const grpc = require('grpc')
const protoLoader = require('@grpc/proto-loader')
const path = require('path')

// 加载 proto 文件并配置
const packageDefinition = protoLoader.loadSync(
  path.resolve(__dirname, '../proto/hello.proto'),
  {
    // 保留现场大小写而不是转换为驼峰格式
    keepCase: true,
    // 长转换类型。有效值为String和Number(全局类型)。默认情况下将复制当前值,这是一个不安全的数字,如果不带{@link Long}且带有长库的话。
    longs: String,
    // 枚举值转换类型。唯一有效的值是`String`(全局类型)。默认情况下复制当前值,即数字ID。
    enums: String,
    // 在结果对象上设置默认值
    defaults: true,
    // 包括设置为当前字段名称的虚拟oneof属性(如果有的话)
    oneofs: true
  }
)

// 使用 grpc 加载包
const helloProto = grpc.loadPackageDefinition(packageDefinition).helloworld

上面这部分仅仅是对 proto 文件的处理,由于 Server 端和 Client 都用同一份 proto,所以这部分代码是服务端和客户端都要用到的。

现在 proto 已经解析好了,我们接着上面的代码开始写一个服务:

// 创建 server
const server = new grpc.Server()

// 添加服务, 这里的服务名叫Hello
server.addService(helloProto.Hello.service, {
  // 实现sayHello方法
  sayHello
})

// sayHello 方法,call 用来获取请求信息,callback 用来向客户端返回信息
function sayHello(call, callback) {
  try {
    // 获取 name 和 age
    const { name, age, job } = call.request
    console.log('收到客户端传值:', name, age, job)
    // 按 proto 约定传值,返回`我叫${name},年龄${age}`
    callback && callback(null, { message: `我叫${name},年龄${age}` })
  } catch (error) {
    console.log('服务出错', error)
    callback && callback(error)
  }
}

// 异步启动
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
    server.start()
    console.log('server start...')
})

打开控制台,输入 node server/index.js运行该服务

Client

因为 Client 也需要解析 proto,上文已经有相关代码,这里只写 Client 余下的代码:

// 创建客户端
const client = new helloProto.Hello(
  'localhost:50051',
  grpc.credentials.createInsecure()
)

// 调用 sayHello 方法
client.sayHello({ name: '张三', age: 30, job: 'teacher' }, (err, response) => {
  if (err) {
    console.log(err)
    return
  }
  const { message } = response
  console.log(message)
})

客户端就这么简单。

现在运行 node client/index.js

客户端调用 sayHello 方法,并传入{ name: '张三', age: 30, job: 'teacher' } ,服务端接收到参数后通过 sayHello 处理,并返回给客户端 我叫张三,年龄30

完整代码见:https://github.com/Vibing/node-grpc

至此,gRPC 一个简单的调用就完成了,就是这么简单。