之前手动实现了一次简陋的 rpc 调用,为了简单使用了 json 编码信息,其实这是非常不可靠的,go 中 json 解析会有一些问题,比如整数会变成浮点数,而且 json 字符串比较占空间。

gRPC 由 google 开发,是一款语言中立、平台中立、开源的 RPC 框架,默认使用 protocol buffers 来序列化和传输消息,基于 http2。

protobuf

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

先编写 proto 文件,再编译成 go 文件,新建 proto/hello.proto 文件

// proto/hello.proto
syntax = "proto3"; package proto; message String {
string value = 1; // 类型 字段 字段标识符
} // 定义服务
service HelloService {
rpc Hello (String) returns (String);
}

安装 protobuf 编译器用于编译 proto 文件,比如使用 scoop 安装 scoop install protobuf,或者手动下载安装

protobuf 编译工具可以把 proto 模板编译成多种语言,默认不支持 go,安装一下 go 插件

$ go get -u github.com/golang/protobuf/proto
$ go get -u github.com/golang/protobuf/protoc-gen-go

进入 hello.proto 文件目录,编译生成 hello.pb.go,后续为了方便可以把编译指令写成 shell 脚本

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

生成的 hello.pb.go 有一些接口和方法(省略了其他的),使用时候只需要实现接口调用对应方法

type HelloServiceServer interface {
Hello(context.Context, *String) (*String, error)
} func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {
s.RegisterService(&_HelloService_serviceDesc, srv)
} type helloServiceClient struct {
cc grpc.ClientConnInterface
} func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient {
return &helloServiceClient{cc}
} func (c *helloServiceClient) Hello(ctx context.Context, in *String, opts ...grpc.CallOption) (*String, error) {
out := new(String)
err := c.cc.Invoke(ctx, "/proto.HelloService/Hello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}

grpc

安装 grpc

$ go get -u google.golang.org/grpc

服务端

在 proto 文件夹同级新建 sever/server.go 文件,实现 server 接口,注册服务

type HelloServiceImpl struct{}

func (p *HelloServiceImpl) Hello(ctx context.Context, args *proto.String) (*proto.String, error) {
reply := &proto.String{Value: "hello " + args.GetValue()}
return reply, nil
} func main() {
grpcServer := grpc.NewServer()
proto.RegisterHelloServiceServer(grpcServer, new(HelloServiceImpl)) lis, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
grpcServer.Serve(lis)
}

客户端

在 proto 文件夹同级新建 sever/server.go 文件,调用服务

func main() {
conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
defer conn.Close() client := proto.NewHelloServiceClient(conn)
reply, err := client.Hello(context.Background(), &proto.String{Value: "world"})
if err != nil {
log.Fatal(err)
}
fmt.Println(reply.GetValue())
}

验证

首先运行服务端 go run server/server.go,再运行客户端

$ go run client/client.go
hello world

这里只简单介绍了用法,关于 protobuf 的更多用法可以参考 https://developers.google.com/protocol-buffers/,关于 protobuf 的原理可以参考 https://blog.csdn.net/carson_ho/article/details/70568606,另外 grpc 还支持流式调用。

流式调用

数据传输是流式的,服务端和服务端不用输完全部数据后才响应,流式传输可以是客户端流式、或服务端流式,也可以同时流式传输,只需要一个 stream 标识

service HelloService {
rpc Hello (String) returns (String);
rpc HelloStream (String) returns (stream String); //服务端流式响应
//rpc HelloStream (stream String) returns (String); //客户端端流式发送
//rpc HelloStream (stream String) returns (stream String); //双向流
}

重新编译,然后在 server/server.go 新增方法

func (HelloServiceImpl) HelloStream(args *proto.String, stream proto.HelloService_HelloStreamServer) error {
for i := 0; i < 10; i++ {
_ = stream.Send(&proto.String{Value: "hello " + args.GetValue() + strconv.Itoa(i)})
}
return nil
}

client/client.go 中调用 HelloStream 方法

stream, _ := client.HelloStream(context.Background(), &proto.String{Value: "World"})
for {
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Printf("stream.Recv: %v", err)
}
log.Printf("%s", res.String())
}

验证,首先运行 server,然后运行 client

$ go run server/server.go

$ go run client/client.go
2020/07/21 07:41:10 value:"hello World0"
2020/07/21 07:41:10 value:"hello World1"
2020/07/21 07:41:10 value:"hello World2"
2020/07/21 07:41:10 value:"hello World3"
2020/07/21 07:41:10 value:"hello World4"
2020/07/21 07:41:10 value:"hello World5"
2020/07/21 07:41:10 value:"hello World6"
2020/07/21 07:41:10 value:"hello World7"
2020/07/21 07:41:10 value:"hello World8"
2020/07/21 07:41:10 value:"hello World9"

最新文章

  1. TypeScript 强类型 JavaScript – Rafy Web 框架选型
  2. spark2.0配置
  3. LUA require 搜索路径指定方法
  4. ubuntu下android开发环境安装
  5. 查看 AndroidManifest.xml文件
  6. QDialog 模态对话框与事件循环(exec其实就是调用了show和eventLoop.exec)
  7. Enum实现单例模式
  8. Grunt 一个专为JavaScript提供的构建工具
  9. linux 安装mysql5.7版本
  10. org.springframework.orm.hibernate3.HibernateSystemException:
  11. java内存模型与volatile变量与Atomic的compareAndSet
  12. Dll重定向(尚存否?)
  13. C++ Curiously Recurring Template Prattern(CRTP)例程
  14. 洛谷P2633 Count on a tree(主席树上树)
  15. Bank Robbery LightOJ - 1163(推方程 注意计算机的计算方式)
  16. CUBA-Platform将全面助力中国开发者
  17. 5.Git版本库创建
  18. Swift 发送邮件和发短信
  19. 编写高质量代码改善C#程序的157个建议——建议111:避免双向耦合
  20. Python—文件

热门文章

  1. Spring 中Controller 获取请求参数的方法笔记
  2. Flutter学习笔记(38)--自定义控件之组合控件
  3. curl模拟调用接口
  4. Idea中SpringBoot整合JSP
  5. JVM的堆内存泄漏排查-性能测试
  6. Windows Defender might be impacting your build performance
  7. npm安装加速
  8. 计算机组成原理Day-1
  9. springboot + rabbitmq 用了消息确认机制,感觉掉坑里了
  10. FTP学习