gRPC
大致的开发流程如下:
1、定义proto文件,即定义Request和Response结构,以及包含多个方法的服务Service。
2、通过protoc工具生成对应语言的Stub。
3、server实现proto中定义的接口,编写逻辑代码。
4、client通过生成的Stub调用server。
Go编写一个获取服务器时间的rpc接口,time.proto
文件的内容如下:
syntax = "proto3";
package proto;
option go_package = "./base;base";
service BaseService {
rpc GetTime (TimeRequest) returns (TimeResponse) {}
}
message TimeRequest {}
message TimeResponse {
string time = 1;
}
通过protoc生成服务端以及客户端的Stub代码:
$ protoc --go_out=plugins=grpc:. time.proto
编写server.go
文件:
package main
import (
"context"
pb "demo/base"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"net"
"time"
)
type service struct {
// time.pb.go中的结构体
pb.UnimplementedBaseServiceServer
}
func main() {
listen, err := net.Listen("tcp", ":50051")
fmt.Println("Listen 50051!")
if err != nil {
fmt.Println(err)
}
s := grpc.NewServer()
reflection.Register(s)
pb.RegisterBaseServiceServer(s, &service{})
s.Serve(listen)
}
// 实现接口
func (s *service) GetTime(ctx context.Context, in *pb.TimeRequest) (*pb.TimeResponse, error) {
now := time.Now().Format("2006-01-02 15:04:05")
return &pb.TimeResponse{Time: now}, nil
}
# 启动server
$ go run server.go
Listen 50051!
编写client.go
文件:
package main
import (
"context"
pb "demo/base"
"fmt"
"google.golang.org/grpc"
"time"
)
func main() {
conn, err := grpc.Dial(":50051", grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
fmt.Println(err)
}
defer conn.Close()
c := pb.NewBaseServiceClient(conn)
getTime(c)
}
func getTime(client pb.BaseServiceClient) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, _ := client.GetTime(ctx, &pb.TimeRequest{})
fmt.Println("Time:", r.Time)
return nil
}
# 启动client
$ go run client.go
Time: 2023-02-10 16:14:01
最终的项目结构:
$ tree demo
demo
├── base
│ └── time.pb.go
├── client.go
├── go.mod
├── go.sum
├── server.go
└── time.proto
1 directory, 6 files
按照惯例,这里也从一个Hello项目开始,本项目定义了一个Hello Service,客户端发送包含字符串名字的请求,
服务端返回Hello消息。
// 指定proto版本
// 表明使用proto3语法,如果你没有指定这个,编译器会使用proto2语法
syntax = "proto3";
// 指定默认包名
package hello;
// 指定golang包名
option go_package = "./hello";
// 定义Hello服务
service Hello {
// 定义SayHello方法
rpc SayHello(HelloRequest) returns (HelloResponse) {}
}
// HelloRequest 请求结构
message HelloRequest {
string name = 1;
}
// HelloResponse 响应结构
message HelloResponse {
string message = 1;
}
hello.proto
文件中定义了一个Hello Service,该服务包含一个SayHello
方法,同时声明了HelloRequest
和
HelloResponse
消息结构用于请求和响应。客户端使用HelloRequest
参数调用SayHello
方法请求服务端,服
务端响应HelloResponse
消息。一个最简单的服务就定义好了。
# 编译hello.proto
$ protoc -I . --go_out=plugins=grpc:. ./hello.proto
在当前目录内生成的hello.pb.go
文件,按照.proto
文件中的说明,包含服务端接口HelloServer
描述,客户
端接口及实现HelloClient
,及HelloRequest
、HelloResponse
结构体。
package main
import (
"context"
"fmt"
"log"
"net"
// 引入编译生成的包
pb "demo/hello"
"google.golang.org/grpc"
)
const (
// Address gRPC服务地址
Address = ":50052"
)
type server struct {
pb.UnimplementedHelloServer
}
// SayHello 实现Hello服务接口
func (s* server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
resp := new(pb.HelloResponse)
resp.Message = fmt.Sprintf("Hello %s.", in.Name)
return resp, nil
}
func main() {
listen, err := net.Listen("tcp", Address)
log.Println("Listen ", Address)
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
// 实例化 grpc Server
s := grpc.NewServer()
// 注册HelloService
pb.RegisterHelloServer(s, &server{})
if err := s.Serve(listen); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
实例化grpc Server并注册HelloService,开始提供服务。
# 启动服务
$ go run server.go
2023/02/10 18:26:39 Listen :50052
package main
import (
pb "demo/hello" // 引入proto包
"golang.org/x/net/context"
"google.golang.org/grpc"
"log"
)
const (
// Address gRPC服务地址
Address = ":50052"
)
func main() {
// 连接
conn, err := grpc.Dial(Address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalln(err)
}
defer conn.Close()
// 初始化客户端
c := pb.NewHelloClient(conn)
// 调用方法
req := &pb.HelloRequest{Name: "gRPC"}
res, err := c.SayHello(context.Background(), req)
if err != nil {
log.Fatalln(err)
}
log.Println(res.Message)
}
客户端初始化连接后直接调用hello.pb.go
中实现的SayHello
方法,即可向服务端发起请求,使用姿势就像调
用本地方法一样。
# 启动客户端
$ go run client.go
2023/02/10 18:30:08 Hello gRPC.
如果你收到了Hello gRPC
的回复,证明该demo成功运行。
# 项目结构
$ tree demo
demo
├── client.go
├── go.mod
├── go.sum
├── hello
│ └── hello.pb.go
├── hello.proto
└── server.go
1 directory, 6 files