5

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

 1 year ago
source link: https://www.yangyanxing.com/article/how-to-use-rpc-part3.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

2022-09-29 Go

2004

4分钟

前两篇文章介绍了在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 中为
go install github.com/golang/protobuf/protoc-gen-go@latest

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

  1. 编写 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来生成对应的文件,

这里几个注意的问题

  • 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 结构体进行序列化与反序列化

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)

}

思考几个问题

  1. 服务端可不可以有多个服务? 如上面的greeter, 如果还有一个order 服务,该怎样集成在一起? 客户端该怎么调用?
  2. 调用过程中如果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)
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK