使用golang进行rpc开发之三使用grpc进行开发
前两篇文章介绍了在golang中使用原生的rpc和使用json做为序列化工具进行rpc开发,但是它们都有着各自的缺点,原生的rpc不能跨语言,json在序列化和反序列化时效率又不高,所以本文我们再来学习一下grpc的使用,它完美的解决了它们的缺点
- 安装protoc
https://github.com/protocolbuffers/protobuf/releases
将下载的可执行文件放到对应系统的环境变量中,windows 中可以将protoc 目录添加到环境变量,mac 中可以将protoc 文件放到 /usr/local/bin
目录下,输入 protoc --version
来验证一下是否执行成功。
-
安装对应语言的插件, golang 中为
| go install github.com/golang/protobuf/protoc-gen-go@latest
|
安装成功之后会在$GOPATH/bin/
目录下生成一个protoc-gen-go 文件
-
编写 proto 文件
proto 文件与go 中的结构体一一对应,如果我们在go 中定义一个User的结构体
| type User struct{
Name string
Age int
Frends []string
}
|
那么我们可以在对应的proto文件中定义相应的message
| syntax = "proto3";
option go_package = "./;user";
message userinfo{
string name = 1;
int32 age = 2;
repeated string frends =3;
}
|
切换到proto文件所在的目录中,然后使用 protoc --go_out=./ *.proto
来生成对应的文件,
这里几个注意的问题
生成的go文件其实就是定义了一些结构体,与生成一些Geter方法,目前来看,还没有任何接口方法,那是因为我们并未在proto中定义 userinfo 需要实现的方法。
proto 的序列化与反序列化,通过 proto.Marshal
与 proto.Unmarshal
可以将proto文件生成的Userinfo 结构体进行序列化与反序列化
| 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
| 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 服务
| // 定义rpc 服务
service UserService{
rpc GetUserInfosss(userid) returns (userinfo);
}
|
这时的编译命令和之前的不一样了,如果还是用之前的编译命令,则不会生成rpc 服务的接口, 需要添加上plugins 参数
| protoc --go_out=plugins=grpc:./proto ./proto/*.proto
|
生成的pb.go 文件里默认生成了服务端与客户端的接口定义
| type UserServiceClient interface {
GetUserInfosss(ctx context.Context, in *Userid, opts ...grpc.CallOption) (*Userinfo, error)
}
type UserServiceServer interface {
GetUserInfosss(context.Context, *Userid) (*Userinfo, error)
}
|
还有两个重要的方法, 后面创建rpc 服务提供了方便的调用
| // 生成客户端实例
func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
return &userServiceClient{cc}
}
// 注册服务端
func RegisterUserServiceServer(s *grpc.Server, srv UserServiceServer) {
s.RegisterService(&_UserService_serviceDesc, srv)
}
|
官方greeter 实例的实现
proto 文件
| syntax = "proto3";
option go_package = "./pb;";
service Greeter {
rpc SayHello(HelloReq) returns (HelloRes);
}
message HelloReq{
string name = 1;
}
message HelloRes{
string message=1;
}
|
定义一个服务,里面只有一个rpc 方法
服务端
| 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)
}
|
客户端
| 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)
}
|
思考几个问题
- 服务端可不可以有多个服务? 如上面的greeter, 如果还有一个order 服务,该怎样集成在一起? 客户端该怎么调用?
- 调用过程中如果tcp 断开了会如何? 或者说两次请求,是每次都创建新的tcp 连接吗?
服务端
| 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)
}
|
客户端
| 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)
}
|