目录

使用golang进行rpc开发之三使用grpc进行开发

前两篇文章介绍了在golang中使用原生的rpc和使用json做为序列化工具进行rpc开发,但是它们都有着各自的缺点,原生的rpc不能跨语言,json在序列化和反序列化时效率又不高,所以本文我们再来学习一下grpc的使用,它完美的解决了它们的缺点

  1. 安装protoc

https://github.com/protocolbuffers/protobuf/releases 将下载的可执行文件放到对应系统的环境变量中,windows 中可以将protoc 目录添加到环境变量,mac 中可以将protoc 文件放到 /usr/local/bin 目录下,输入 protoc --version 来验证一下是否执行成功。

  1. 安装对应语言的插件, golang 中为
1
go install github.com/golang/protobuf/protoc-gen-go@latest

安装成功之后会在$GOPATH/bin/ 目录下生成一个protoc-gen-go 文件

  1. 编写 proto 文件

proto 文件与go 中的结构体一一对应,如果我们在go 中定义一个User的结构体

1
2
3
4
5
type User struct{
	Name string
	Age int
	Frends []string
}

那么我们可以在对应的proto文件中定义相应的message

1
2
3
4
5
6
7
8
syntax = "proto3";
option go_package = "./;user";

message userinfo{
    string name = 1;
    int32 age = 2;
    repeated string frends =3;
}

切换到proto文件所在的目录中,然后使用 protoc --go_out=./ *.proto来生成对应的文件,

这里几个注意的问题

  • proto 中定义message 时,我们即使用了小写字母,使用protoc 生成的go文件也会变成大写,如上面的userinfo 会变为Userinfo, name=>Name, age=>Age, frends=>Frends
  • 第一行 syntax = "proto3";为固定写法,指定proto的版本,目前基本上都是用的3版本。
  • 生成的文件以proto文件的名称加上.pb.go,如上面的proto文件为user.proto, 生成的go 文件名为user.pb.go, 包名为 go_package中指定的包名。
  1. 生成的go 文件有什么用?

生成的go文件其实就是定义了一些结构体,与生成一些Geter方法,目前来看,还没有任何接口方法,那是因为我们并未在proto中定义 userinfo 需要实现的方法。

proto 的序列化与反序列化,通过 proto.Marshalproto.Unmarshal可以将proto文件生成的Userinfo 结构体进行序列化与反序列化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
	"fmt"
	"server/proto/userinfo"

	"google.golang.org/protobuf/proto"
)

type User struct {
	Name   string
	Age    int
	Frends []string
}

func main() {
	var u = &userinfo.Userinfo{
		Name:   "yyx",
		Age:    18,
		Frends: []string{"aaa", "bbb"},
	}
	// 序列化
	data, err := proto.Marshal(u)
	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println(data) // []byte 二进制数据
	}

	// 反序列化
	var user userinfo.Userinfo
	err = proto.Unmarshal(data, &user)
	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println(&user)
	}
}

message 可嵌套 message

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
message userinfo{
    string name = 1;
    int32 age = 2;
    repeated string frends =3;
}

// 结构体嵌套, 某个message 可以嵌套别的结构体
message Oderinfo{
    int32 id =1;
    string address =2;
    userinfo userinfo=3;
}

定义rpc 服务

1
2
3
4
//  定义rpc 服务
service UserService{
    rpc GetUserInfosss(userid) returns (userinfo);
}

这时的编译命令和之前的不一样了,如果还是用之前的编译命令,则不会生成rpc 服务的接口, 需要添加上plugins 参数

1
protoc  --go_out=plugins=grpc:./proto ./proto/*.proto

生成的pb.go 文件里默认生成了服务端与客户端的接口定义

1
2
3
4
5
6
7
type UserServiceClient interface {
	GetUserInfosss(ctx context.Context, in *Userid, opts ...grpc.CallOption) (*Userinfo, error)
}

type UserServiceServer interface {
	GetUserInfosss(context.Context, *Userid) (*Userinfo, error)
}

还有两个重要的方法, 后面创建rpc 服务提供了方便的调用

1
2
3
4
5
6
7
8
9
// 生成客户端实例
func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
	return &userServiceClient{cc}
}

// 注册服务端
func RegisterUserServiceServer(s *grpc.Server, srv UserServiceServer) {
	s.RegisterService(&_UserService_serviceDesc, srv)
}

官方greeter 实例的实现

proto 文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
syntax = "proto3";
option go_package = "./pb;";

service Greeter {
    rpc SayHello(HelloReq) returns (HelloRes);
}

message HelloReq{
    string name = 1;
}

message HelloRes{
    string message=1;
}

定义一个服务,里面只有一个rpc 方法

服务端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"server/proto/pb"

	"google.golang.org/grpc"
)

type hello struct{}

// 定义 SayHello的实现
func (h hello) SayHello(ctx context.Context, in *pb.HelloReq) (*pb.HelloRes, error) {
	fmt.Println(in)
	res := &pb.HelloRes{
		Message: "Hello " + in.Name,
	}
	return res, nil
}

func main() {
	// 1. 初始化grpc 对象
	server := grpc.NewServer()
	// 2. 注册服务
	pb.RegisterGreeterServer(server, &hello{})
	// 3. 设置监听
	listener, err := net.Listen("tcp", "127.0.0.1:8080")
	if err != nil {
		log.Fatal(err.Error())
	}
	// 启动服务
	server.Serve(listener)
}

客户端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
	"client/proto/pb"
	"context"
	"fmt"
	"log"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	// 与grpc 服务进行连接
	client, err := grpc.Dial("127.0.0.1:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatal(err)
	}
	// 初始化client
	gclient := pb.NewGreeterClient(client)

	// 进行调用
	res, err := gclient.SayHello(context.Background(), &pb.HelloReq{
		Name: "杨彦星",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Message)

}

思考几个问题

  1. 服务端可不可以有多个服务? 如上面的greeter, 如果还有一个order 服务,该怎样集成在一起? 客户端该怎么调用?
  2. 调用过程中如果tcp 断开了会如何? 或者说两次请求,是每次都创建新的tcp 连接吗?

服务端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"server/proto/pb"

	"google.golang.org/grpc"
)

type hello struct{}

// 定SayHello的实现
func (h hello) SayHello(ctx context.Context, in *pb.HelloReq) (*pb.HelloRes, error) {
	fmt.Println(in)
	res := &pb.HelloRes{
		Message: "Hello " + in.Name,
	}
	return res, nil
}

type user struct{}

func (u user) GetUserInfosss(ctx context.Context, in *pb.Userid) (*pb.Userinfo, error) {
	fmt.Println(in)
	res := pb.Userinfo{
		Name:   "yangyanxing",
		Age:    in.Id,
		Frends: []string{"fjy", "zhangsan"},
	}
	return &res, nil
}

func main() {
	// 1. 初始化grpc 对象
	server := grpc.NewServer()
	// 2. 注册服务
	pb.RegisterGreeterServer(server, &hello{})
	pb.RegisterUserServiceServer(server, &user{})

	// 3. 设置监听
	listener, err := net.Listen("tcp", "127.0.0.1:8080")
	if err != nil {
		log.Fatal(err.Error())
	}
	// 启动服务
	server.Serve(listener)
}

客户端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
	"client/proto/pb"
	"context"
	"fmt"
	"log"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	// 与grpc 服务进行连接
	client, err := grpc.Dial("127.0.0.1:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatal(err)
	}
	// 初始化client
	gclient := pb.NewGreeterClient(client)
	uclient := pb.NewUserServiceClient(client)

	// 进行调用
	res, err := gclient.SayHello(context.Background(), &pb.HelloReq{
		Name: "杨彦星",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Message)
	// 调用 user相关的rpc方法
	result, err := uclient.GetUserInfosss(context.Background(), &pb.Userid{Id: 1})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%#v", result)
}
  • 文章标题: 使用golang进行rpc开发之三使用grpc进行开发
  • 本文作者: 杨彦星
  • 本文链接: https://www.yangyanxing.com/article/how-to-use-rpc-part3.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。