第一个gRPC示例
使用gRPC开发的3个步骤
- 编写
protobuf
文件 - 生成代码【包括服务端和客户端】
- 编写业务逻辑,会使用第二步生成的代码
示例:第一步编写proto
文件
我们新建一个hello_server
项目,一般我们创建的protobuf
文件存储在pb
或者proto
文件夹下,然后我们还需要使用mod
来初始化一个项目,在pb
文件下新建一个hello.proto
syntax = "proto3"; // 版本声明
// 生成的代码之后引入路径
option go_package = "hello_server/pb";
// proto 文件模块
package pb;
// 定义消息
message HelloRequest {
string name = 1; // 字段类型 字段名称 序号
}
message HelloResponse {
string replay = 1;
}
// 定义服务
service Greeter {
// 对外提供的方法
rpc SayHello(HelloRequest) returns (HelloResponse){}
}
示例:第二步,生成代码【服务端】
在项目根目录下执行以下命令,根据hello.proto
生成go
源码文件
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
pb/hello.proto
--go_out
:指定生成的go
源码文件到当前项目目录--go_opt
:使用的模式是使用的相对路径的模式,这里即相对于pb
目录下--go-grpc_out
:生成的gRPC
的源码文件目录--go-grpc_opt
:相对路径模式
注意
如果你的终端不支持\
,你就写成一行然后再进行赋值。
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative pb/hello.proto
├── go.mod
└── pb
├── hello.pb.go
├── hello.proto
└── hello_grpc.pb.go
示例:第三步编写业务逻辑
需要使用到生产的代码的时候,我们得先下载一下依赖
go mod tidy
我们下面的步骤就是编写业务代码,我们需要创建一个main.go
,我们通常需要自己再定义一个服务结构体,去实现第二步生成的代码中的方法,最后使用net
和gRPC
来启动和注册服务。
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"hello_server/pb"
"net"
)
// 启动一个 gRPC 服务
type server struct {
pb.UnimplementedGreeterServer // 没有实现的服务的结构体
// ... 其他的字段
}
// SayHello 使用我们自己的结构体去实现 SayHello 方法
// 这个方法是我们对外提供的服务
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
reply := "hello " + in.Name
return &pb.HelloResponse{Replay: reply}, nil
}
func main() {
// 本地启动服务
l, err := net.Listen("tcp", ":8972")
if err != nil {
fmt.Printf("failed to listen, :err:%v\n", err)
return
}
// 启动rpc服务,创建gRPC服务
s := grpc.NewServer()
// 注册服务
pb.RegisterGreeterServer(s, &server{})
// 启动服务
err = s.Serve(l)
if err != nil {
fmt.Printf("failed to server, err:%v\n", err)
return
}
}
编写客户端
再新建一个hello_client
项目,同样也需要使用mod
进行初始化
go mod init hello_client
为了便携效果,直接把服务端的pb
文件和生成的代码文件直接拷贝过来即可,因为你客户端需要调用服务端的代码,基本得一样,只是proto
文件里的go_package
可能需要小小变动一下,你可以自己再重新生成代码。
注意
proto
文件中的package pb;
必须和server端一致option go_package = "hello_client/pb";
这里的hello_client
视你go mod init
的时候的名称是什么,这里就是什么
继续生成客户端的代码
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
pb/hello.proto
编写客户端逻辑
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"hello_client/pb"
"log"
"time"
)
// grpc 客户端
// 调用 server 端的 SayHello 方法
func main() {
// 连接服务端
conn, err := grpc.Dial("127.0.0.1:8972", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("grpc.Dail failed, err: %v\n", err)
}
defer conn.Close()
// 创建客户端
client := pb.NewGreeterClient(conn)
// 调用rpc方法
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
name := "无解"
resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Printf("client.SayHello faield, err:%v\n", err)
}
log.Printf("resp: %v\n", resp.Replay)
log.Printf("resp: %v\n", resp.GetReplay())
}
使用命令行参数解析来动态传参
package main
import (
"context"
"flag"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"hello_client/pb"
"log"
"time"
)
// grpc 客户端
// 调用 server 端的 SayHello 方法
var name = flag.String("name", "无解", "通过-name告诉服务端你是谁")
func main() {
// 解析命令行参数
flag.Parse()
// 连接服务端
conn, err := grpc.Dial("127.0.0.1:8972", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("grpc.Dail failed, err: %v\n", err)
}
defer conn.Close()
// 创建客户端
client := pb.NewGreeterClient(conn)
// 调用rpc方法
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
log.Printf("client.SayHello faield, err:%v\n", err)
}
log.Printf("resp: %v\n", resp.Replay)
log.Printf("resp: %v\n", resp.GetReplay())
}
➜ hello_client ./hello_client -name 哈哈哈
2022/10/26 21:46:55 resp: hello 哈哈哈
2022/10/26 21:46:55 resp: hello 哈哈哈