目录
一、grpc和protobuf简介
- 什么是grpc:gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持
- grpc官网地址:https://github.com/grpc/grpc
- grpc-go官网地址:github搜索“grpc-go” -> All GitHub;https://github.com/grpc/grpc-go
- grpc通信
- 什么是protobuf:Protocol Buffer 其实 是 Google出品的一种轻量 & 高效的结构化数据存储格式,性能比 Json、XML 真的强!太!多!;protobuf经历了protobuf2和protobuf3,pb3比pb2简化了很多,目前主流的版本是pb3
- protobuf优缺点
二、grpc环境搭建
三、go使用protobuf
1 - go简单使用protobuf
- 新建一个proto文件:helloworld.proto(注意后缀是proto)
syntax = "proto3";
package proto; //当前包名
option go_package = "./";
message HelloRequest{
string name = 1; // 1是编号不是值
}
- 打开命令行
- cmd下执行命令生成go文件:
protoc --go_out=. *.proto
- main.go测试:注意proto生成的时候helloworld.pb.go包名是
package __
package main
import (
"encoding/json"
"fmt"
"github.com/golang/protobuf/proto"
"MyTestProject/helloworld/proto"
)
type Hello struct {
Name string `json:"name"`
}
func main() {
req := __.HelloRequest{
Name: "test",
}
rsp, _ := proto.Marshal(&req)
fmt.Println("proto len = ", len(rsp))
jsonStruct := Hello{Name: "test"}
jsonRsp, _ := json.Marshal(jsonStruct)
fmt.Println("json len = ", len(jsonRsp))
}
2 - 进一步对比protobuf和json
- helloworld.proto
syntax = "proto3";
package proto; //当前包名
option go_package = "./";
message HelloRequest{
string name = 1; // 1是编号不是值
int32 age = 2;
repeated string courses = 3; //repeated代表可重复的,切片
}
- main.go
package main
import (
"encoding/json"
"fmt"
"github.com/golang/protobuf/proto"
"MyTestProject/helloworld/proto"
)
type Hello struct {
Name string `json:"name"`
Age int `json:"age"`
Courses []string `json:"courses"`
}
func main() {
req := __.HelloRequest{
Name: "test",
Age: 18,
Courses: []string{"go", "gin", "微服务"},
}
rsp, _ := proto.Marshal(&req)
fmt.Println("proto len = ", len(rsp))
jsonStruct := Hello{
Name: "test",
Age: 18,
Courses: []string{"go", "gin", "微服务"},
}
jsonRsp, _ := json.Marshal(jsonStruct)
fmt.Println("json len = ", len(jsonRsp))
}
3 - proto的序列化与反序列化
package main
import (
"fmt"
"github.com/golang/protobuf/proto"
"MyTestProject/helloworld/proto"
)
func main() {
req := __.HelloRequest{
Name: "test",
Age: 18,
Courses: []string{"go", "gin", "微服务"},
}
rsp, _ := proto.Marshal(&req)
fmt.Println("proto len = ", len(rsp))
// 反序列化proto
newReq := __.HelloRequest{}
_ = proto.Unmarshal(rsp, &newReq)
fmt.Println(newReq.Name, newReq.Age, newReq.Courses)
}
4 - grpc的proto编写与生成
- grpc的proto文件:helloworld.proto
syntax = "proto3";
package proto; //当前包名
option go_package = "./";
service Hello{
rpc Hello(HelloRequest) returns (Response); // hello接口
}
message HelloRequest{
string name = 1; // 1是编号不是值
int32 age = 2;
repeated string courses = 3; //repeated代表可重复的,切片
}
message Response{
string reply = 1;
}
- grpc生成:这个与之前的非grpc的相似,但是生成的命令需要修改为
protoc --go-grpc_out=. *.proto
(这个就是为什么在grpc环境搭建中我们需要下载与修改protoc-gen-go-grpc.exe这个文件)
四、go的grpc快速体验
- helloworld.proto:
- 生成命令
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative *.proto
- 注意最新版本的grpc将pb.go拆分成2份文件,分别是proto -> helloworld.pb.go,grpc -> helloworld_grpc.pb.go
- 生成命令
syntax = "proto3";
option go_package = ".;proto";
// 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;
}
- server:注意最新版本的grpc需要在Server的结构中需要带上
proto.UnimplementedGreeterServer
否则会报错
package main
import (
"context"
"net"
"google.golang.org/grpc"
"test_project/grpc_test/proto"
)
type Server struct{
proto.UnimplementedGreeterServer
}
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
error) {
return &proto.HelloReply{
Message: "hello " + request.Name,
}, nil
}
func main() {
g := grpc.NewServer()
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:8088")
if err != nil {
return
}
err = g.Serve(lis)
if err != nil {
return
}
}
- client
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"test_project/grpc_test/proto"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:8088", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "test"})
if err != nil {
return
}
fmt.Println(r.Message)
}
五、grpc的流模式
-
grpc有四种流模式:
- 简单模式(Simple RPC):这种模式最为传统,即客户端发起一次请求,服务端响应一个数据,这和大家平时熟悉的RPC没有什么大的区别,所以不再详细介绍
- 服务端数据流模式(Server-side streaming RPC):这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。
- 客户端数据流模式(Client-side streaming RPC):与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数
据流,而在发送结束后,由服务端返回一个响应。典型的例子是物联网终端向服务器报送数据(比如热感应装置要一直把温度发送给服务端)。 - 双向数据流模式(Bidirectional streaming RPC): 顾名思义,这是客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是可以实现实时交互。典型的例子是聊天机器人。
-
项目结构
-
stream.proto
syntax = "proto3";
option go_package = ".;proto";
service Greeter {
rpc GetStream(StreamReqData) returns (stream StreamResData); //服务端流模式
rpc PutStream(stream StreamReqData) returns (StreamResData); //客户端流模式
rpc AllStream(stream StreamReqData) returns (stream StreamResData); //双向流模式
}
message StreamReqData {
string data = 1;
}
message StreamResData {
string data = 1;
}
- server.go
package main
import (
"fmt"
"net"
"sync"
"time"
"google.golang.org/grpc"
"test_project/stream_grpc_test/proto"
)
const PORT = ":50052"
type server struct {
proto.UnimplementedGreeterServer
}
func (s *server) GetStream(req *proto.StreamReqData, res proto.Greeter_GetStreamServer) error {
i := 0
for {
i++
_ = res.Send(&proto.StreamResData{
Data: fmt.Sprintf("%v", time.Now().Unix()),
})
time.Sleep(time.Second)
if i > 10 {
break
}
}
return nil
}
func (s *server) PutStream(cliStr proto.Greeter_PutStreamServer) error {
for {
if a, err := cliStr.Recv(); err != nil {
fmt.Println(err)
break
} else {
fmt.Println(a.Data)
}
}
return nil
}
func (s *server) AllStream(allStr proto.Greeter_AllStreamServer) error {
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
for {
data, _ := allStr.Recv()
fmt.Println("收到客户端消息:" + data.Data)
}
}()
go func() {
defer wg.Done()
for {
_ = allStr.Send(&proto.StreamResData{Data: "我是服务器"})
time.Sleep(time.Second)
}
}()
wg.Wait()
return nil
}
func main() {
lis, err := net.Listen("tcp", PORT)
if err != nil {
return
}
s := grpc.NewServer()
proto.RegisterGreeterServer(s, &server{})
err = s.Serve(lis)
if err != nil {
return
}
}
- client.go
package main
import (
"context"
"fmt"
"google.golang.org/grpc/credentials/insecure"
"sync"
"time"
"google.golang.org/grpc"
"test_project/stream_grpc_test/proto"
)
func main() {
conn, err := grpc.Dial("localhost:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return
}
defer conn.Close()
//服务端流模式
c := proto.NewGreeterClient(conn)
res, _ := c.GetStream(context.Background(), &proto.StreamReqData{Data: "学习"})
for {
a, err := res.Recv() //如果大家懂socket编程的话就明白 send recv
if err != nil {
fmt.Println(err)
break
}
fmt.Println(a.Data)
}
//客户端流模式
putS, _ := c.PutStream(context.Background())
i := 0
for {
i++
_ = putS.Send(&proto.StreamReqData{
Data: fmt.Sprintf("学习%d", i),
})
time.Sleep(time.Second)
if i > 10 {
break
}
}
//双向流模式
allStr, _ := c.AllStream(context.Background())
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
for {
data, _ := allStr.Recv()
fmt.Println("收到客户端消息:" + data.Data)
}
}()
go func() {
defer wg.Done()
for {
_ = allStr.Send(&proto.StreamReqData{Data: "学习"})
time.Sleep(time.Second)
}
}()
wg.Wait()
}