返回

Go学习笔记04-GRPC

GRPC安装和生成代码

注:

  1. Goland安装protobuf插件
  2. 新建三个Packageclientserverpb
  3. pb包下新建hello_grpc.proto文件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
syntax = "proto3";
option go_package = "./;hello_grpc";
package hello_grpc;

message Req {
  string message = 1;
}
message Res {
  string message = 1;
}

service HelloGRPC{
  // SayHey 方法名 Req 入参 Res 出参
  rpc SayHey(Req) returns (Res);
}
  1. 安装grpc和proto的解释器(编译工具),在当前pb的上两级目录执行命令:
1
2
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

这俩安装完后,会自动生成两个可执行文件protoc-gen-go.exeprotoc-gen-go-grpc.exe,并放到$GOPATHbin目录下

  1. 在当前pb的上一级目录执行下面命令,拉取grpc代码包
1
go get google.golang.org/grpc
  1. pb目录下创建build.bat文件:
1
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./hello_grpc.proto
  1. 添加bin目录的绝对路径到环境变量Path
  2. 进入pb目录,打开powershell(在Goland的终端中可能会报错,在文件夹下右键打开终端),执行下面命令编译proto,在当前目录下生成hello_grpc.pb.gohello_grpc_grpc.pb.go文件:
1
.\build.bat
  1. 自此即通过proto自动生成grpc的方法

编写客户端和服务端

server.go

 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
package main

import (
    "context"
    "fmt"
    hello_grpc "goclass/pb"
    "google.golang.org/grpc"
    "net"
)

//取出server
type server struct {
    hello_grpc.UnimplementedHelloGRPCServer
}

//挂载方法
func (s *server) SayHey(ctx context.Context, req *hello_grpc.Req) (res *hello_grpc.Res, err error) {
    // 客户端调用时的处理逻辑
    fmt.Println(req.GetMessage())
    return &hello_grpc.Res{Message: "我是从服务端返回的grpc的内容"}, nil
}

func main() {
    //1.注册监听
    l, _ := net.Listen("tcp", ":8888")
    //2.注册一个server
    s := grpc.NewServer()
    hello_grpc.RegisterHelloGRPCServer(s, &server{})
    s.Serve(l)
}

client.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "context"
    "fmt"
    hello_grpc "goclass/pb"
    "google.golang.org/grpc"
)

func main() {
    //建立一个连接到服务器
    conn, _ := grpc.Dial("localhost:8888", grpc.WithInsecure())
    //最后要关闭连接
    defer conn.Close()
    //new一个客户端
    client := hello_grpc.NewHelloGRPCClient(conn)
    //调用client的方法
    req, _ := client.SayHey(context.Background(), &hello_grpc.Req{Message: "我从客户端来"})
    //打印返回信息
    fmt.Println(req.GetMessage())
}

客户端运行结果:

服务端运行结果:

protobuf的基础使用方法

  1. 保证有go.mod文件,若没有的话,使用go mod init生成
  2. src下新建pb文件夹,这里我由于上面建过pb,所以我新建pb_1文件夹(下文统一用pb指代)
  3. pb文件夹下给protobuf做好分包:在pb下新建person文件夹,在person文件夹下新建person.proto文件
 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
syntax = "proto3";//声明语法版本 告诉编译器用proto3来解读

package person;//给当前proto分配包名称 这个和go的包不一样

option go_package = "goclass/pb/person;person";//包路径(从mod里的module下开始写);别名

message Person{
    //声明格式:类型 key(下划线) = 唯一(标识)
    //类比到具体语言中的数据类型:
    //https://protobuf.dev/programming-guides/proto3/#scalar
    string name = 1;
    int32 age = 2;
    bool sex = 3;
    //声明切片(数组) 关键字repeated
    repeated string test = 4;
    //声明map格式:map<key,value> key = 标识;
    map<string, string> test_map = 5;
}
//类型嵌套
message Home{
    repeated Person persons = 1;
    message visitor{
    	string name = 1;
    }
    Visitor visitor = 2;
}
  1. pb目录下创建build.bat文件:
1
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./person/person.proto
  1. 添加bin目录的绝对路径到环境变量Path中(上面做过的就不用再添加了)
  2. 进入pb目录,打开powershell(在Goland的终端中可能会报错,在文件夹下右键打开终端),执行下面命令编译proto,在当前目录下生成person.pb.go文件:
1
.\build.bat
  1. 自此即通过proto自动生成grpc的方法
  2. 可以看到在person.pb.go文件中生成了对应的结构体
 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
type Person struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    //声明格式:类型 key(下划线) = 唯一(标识)
    //类比到具体语言中的数据类型:
    //https://protobuf.dev/programming-guides/proto3/#scalar
    Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    Age  int32  `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
    Sex  bool   `protobuf:"varint,3,opt,name=sex,proto3" json:"sex,omitempty"`
    //声明切片(数组) 关键字repeated
    Test []string `protobuf:"bytes,4,rep,name=test,proto3" json:"test,omitempty"`
    //声明map格式:map<key,value> key = 标识;
    TestMap map[string]string `protobuf:"bytes,5,rep,name=test_map,json=testMap,proto3" json:"test_map,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
//类型嵌套
type Home struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Persons []*Person     `protobuf:"bytes,1,rep,name=persons,proto3" json:"persons,omitempty"`
    Visitor *Home_Visitor `protobuf:"bytes,2,opt,name=visitor,proto3" json:"visitor,omitempty"`
}

交叉引用

  1. pb新建home文件夹,在home文件夹下新建home.proto文件
1
2
3
4
5
6
7
8
9
syntax = "proto3";//声明语法版本 告诉编译器用proto3来解读

package home;//给当前proto分配包名称 这个和go的包不一样

option go_package = "goclass/pb/home;home";//包路径(从mod里的module下开始写);别名

message Home{
	string home_num = 1;
}
  1. person.proto中引入home.proto
 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
syntax = "proto3";//声明语法版本 告诉编译器用proto3来解读

package person;//给当前proto分配包名称 这个和go的包不一样

option go_package = "goclass/pb_1/person;person";//包路径(从mod里的module下开始写);别名

import "home/home.proto";

message Person{
    //声明格式:类型 key(下划线) = 唯一(标识)
    //类比到具体语言中的数据类型:
    //https://protobuf.dev/programming-guides/proto3/#scalar
    string name = 1;
    int32 age = 2;
    bool sex = 3;
    //声明切片(数组) 关键字repeated
    repeated string test = 4;
    //声明map格式:map<key,value> key = 标识;
    map<string, string> test_map = 5;
    home.Home i_home = 6;
}
//类型嵌套
message Home{
    repeated Person persons = 1;
    message Visitor{
    	string name = 1;
    }
    Visitor visitor = 2;
}
  1. build.bat中加入命令:
1
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./home/home.proto
  1. 可以看到person.pb.go中出现了IHome
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
type Person struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    //声明格式:类型 key(下划线) = 唯一(标识)
    //类比到具体语言中的数据类型:
    //https://protobuf.dev/programming-guides/proto3/#scalar
    Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    Age  int32  `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
    Sex  bool   `protobuf:"varint,3,opt,name=sex,proto3" json:"sex,omitempty"`
    //声明切片(数组) 关键字repeated
    Test []string `protobuf:"bytes,4,rep,name=test,proto3" json:"test,omitempty"`
    //声明map格式:map<key,value> key = 标识;
    TestMap map[string]string `protobuf:"bytes,5,rep,name=test_map,json=testMap,proto3" json:"test_map,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
    IHome   *home.Home        `protobuf:"bytes,6,opt,name=i_home,json=iHome,proto3" json:"i_home,omitempty"`
}

定义服务

  1. person.proto文件中添加:
1
2
3
4
5
6
service SearchService{
    rpc Search(Person) returns (Person); //传统的 即刻响应
    rpc SearchIn(stream Person) returns (Person); //入参为流
    rpc SearchOut(Person) returns (stream Person);//出参为流
    rpc SearchIO(stream Person) returns (stream Person);//出入参均为流
}
  1. pb_1目录下,执行下面命令生成grpc方法:
1
.\build.bat
  1. 注意person_grpc.pb.goSearchServiceClientSearchServiceServer接口中的四个方法:
1
2
3
4
5
6
type SearchServiceClient interface {
    Search(ctx context.Context, in *Person, opts ...grpc.CallOption) (*Person, error)
    SearchIn(ctx context.Context, opts ...grpc.CallOption) (SearchService_SearchInClient, error)
    SearchOut(ctx context.Context, in *Person, opts ...grpc.CallOption) (SearchService_SearchOutClient, error)
    SearchIO(ctx context.Context, opts ...grpc.CallOption) (SearchService_SearchIOClient, error)
}

GRPC server的四种传输模式

紧接上面的定义服务继续学习

普通服务

  • 普通服务就是之前做的那种,客户端调用一次服务端就马上返回一个值
  • 参数是一个入参,一个回参就可以了

代码示例

  1. 删除上面学习中的pb_1中的home目录,修改person_grpc.pb.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
syntax = "proto3";//声明语法版本 告诉编译器用proto3来解读

package person;//给当前proto分配包名称 这个和go的包不一样

option go_package = "goclass/pb_1/person;person";//包路径(从mod里的module下开始写);别名

message PersonReq{
    //声明格式:类型 key(下划线) = 唯一(标识)
    string name = 1;
    int32 age = 2;
}
message PersonRes{
    //声明格式:类型 key(下划线) = 唯一(标识)
    string name = 1;
    int32 age = 2;
}

service PersonService{
    rpc Search(PersonReq) returns (PersonRes); //传统的 即刻响应
    rpc SearchIn(stream PersonReq) returns (PersonRes); //入参为流
    rpc SearchOut(PersonReq) returns (stream PersonRes);//出参为流
    rpc SearchIO(stream PersonReq) returns (stream PersonRes);//出入参均为流
}
  1. 修改build.bat
1
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./person/person.proto
  1. pb_1目录下执行.\build.bat
  2. 编写服务端代码server.go
 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
package main

import (
    "context"
    "goclass/pb_1/person"
    "google.golang.org/grpc"
    "net"
)

//1.取出server
type personServer struct {
    person.UnimplementedPersonServiceServer
}

//2.挂载方法
func (*personServer) Search(ctx context.Context, req *person.PersonReq) (*person.PersonRes, error) {
    name := req.GetName()
    res := &person.PersonRes{Name: "我收到了" + name + "的信息"}
    return res, nil
}
func (*personServer) SearchIn(server person.PersonService_SearchInServer) error {
    return nil
}
func (*personServer) SearchOut(*person.PersonReq, person.PersonService_SearchOutServer) error {
    return nil
}
func (*personServer) SearchIO(person.PersonService_SearchIOServer) error {
    return nil
}

func main() {
    //3.注册服务
    //3.1.注册监听
    l, _ := net.Listen("tcp", ":8888")
    //3.2.注册一个server
    s := grpc.NewServer()
    person.RegisterPersonServiceServer(s, &personServer{})
    //4.创建监听
    s.Serve(l)
}
  1. 编写客户端代码client.go
 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
package main

import (
    "context"
    "fmt"
    "goclass/pb_1/person"
    "google.golang.org/grpc"
)

func main() {
    //建立一个连接到服务器
    conn, _ := grpc.Dial("localhost:8888", grpc.WithInsecure())
    //最后要关闭连接
    defer conn.Close()
    //new一个客户端
    client := person.NewPersonServiceClient(conn)
    //调用client的方法
    //普通服务
    res, err := client.Search(context.Background(), &person.PersonReq{Name: "I'm wyatt"})
    if err != nil {
        //打印返回信息
        fmt.Println(err)
    }
    fmt.Println(res)
}
  1. 启动服务端,客户端发起请求,得到响应:name:"我收到了I'm wyatt的信息"

流式传入(客户端流)

  • 客户端往服务器发消息时是一个流,服务器会一直接收,最后返回接收成功与否
  • 参数是一个流入,一个出参

代码示例

server.go

 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"
    "goclass/pb_1/person"
    "google.golang.org/grpc"
    "net"
)

//1.取出server
type personServer struct {
    person.UnimplementedPersonServiceServer
}

//2.挂载方法
func (*personServer) Search(ctx context.Context, req *person.PersonReq) (*person.PersonRes, error) {
    name := req.GetName()
    res := &person.PersonRes{Name: "我收到了" + name + "的信息"}
    return res, nil
}
func (*personServer) SearchIn(server person.PersonService_SearchInServer) error {
    for {
        //接收并打印请求信息
        req, err := server.Recv()
        fmt.Println(req)
        if err != nil {
            server.SendAndClose(&person.PersonRes{Name: "完成了"})
            break
        }
    }
    return nil
}
func (*personServer) SearchOut(*person.PersonReq, person.PersonService_SearchOutServer) error {
    return nil
}
func (*personServer) SearchIO(person.PersonService_SearchIOServer) error {
    return nil
}

func main() {
    //3.注册服务
    //3.1.注册监听
    l, _ := net.Listen("tcp", ":8888")
    //3.2.注册一个server
    s := grpc.NewServer()
    person.RegisterPersonServiceServer(s, &personServer{})
    //4.创建监听
    s.Serve(l)
}

client.go

 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 (
    "context"
    "fmt"
    "goclass/pb_1/person"
    "google.golang.org/grpc"
    "time"
)

func main() {
    //建立一个连接到服务器
    conn, _ := grpc.Dial("localhost:8888", grpc.WithInsecure())
    //最后要关闭连接
    defer conn.Close()
    //new一个客户端
    client := person.NewPersonServiceClient(conn)
    //调用client的方法
    c, _ := client.SearchIn(context.Background())
    i := 0
    for {
        if i > 10 {
            res, _ := c.CloseAndRecv()
            fmt.Println(res)
            break
        }
        time.Sleep(1 * time.Second)
        c.Send(&person.PersonReq{Name: "我是客户端发来的信息"})
        i++
    }
}

启动服务端,客户端发起请求:服务端每隔1s接收到一次客户端发来的信息我是客户端发来的信息,10s后接收到nil,服务端向客户端返回响应信息完成了

流式返回(服务端流)

  • 客户端告知服务端要取一个流,服务端返回一个流
  • 参数是一个入参,一个流出

server.go

 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
51
52
53
54
55
56
57
58
59
60
61
package main

import (
    "context"
    "fmt"
    "goclass/pb_1/person"
    "google.golang.org/grpc"
    "net"
    "time"
)

//1.取出server
type personServer struct {
    person.UnimplementedPersonServiceServer
}

//2.挂载方法
func (*personServer) Search(ctx context.Context, req *person.PersonReq) (*person.PersonRes, error) {
    name := req.GetName()
    res := &person.PersonRes{Name: "我收到了" + name + "的信息"}
    return res, nil
}
func (*personServer) SearchIn(server person.PersonService_SearchInServer) error {
    for {
        //接收并打印请求信息
        req, err := server.Recv()
        fmt.Println(req)
        if err != nil {
            server.SendAndClose(&person.PersonRes{Name: "完成了"})
            break
        }
    }
    return nil
}
func (*personServer) SearchOut(req *person.PersonReq, server person.PersonService_SearchOutServer) error {
    name := req.Name
    i := 0
    for {
        if i > 10 {
            break
        }
        time.Sleep(1 * time.Second)
        i++
        server.Send(&person.PersonRes{Name: "我拿到了" + name})
    }
    return nil
}
func (*personServer) SearchIO(person.PersonService_SearchIOServer) error {
    return nil
}

func main() {
    //3.注册服务
    //3.1.注册监听
    l, _ := net.Listen("tcp", ":8888")
    //3.2.注册一个server
    s := grpc.NewServer()
    person.RegisterPersonServiceServer(s, &personServer{})
    //4.创建监听
    s.Serve(l)
}

client.go

 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
package main

import (
    "context"
    "fmt"
    "goclass/pb_1/person"
    "google.golang.org/grpc"
)

func main() {
    //建立一个连接到服务器
    conn, _ := grpc.Dial("localhost:8888", grpc.WithInsecure())
    //最后要关闭连接
    defer conn.Close()
    //new一个客户端
    client := person.NewPersonServiceClient(conn)
    //调用client的方法
    c, _ := client.SearchOut(context.Background(), &person.PersonReq{Name: "wyatt"})
    for {
        req, err := c.Recv()
        if err != nil {
            fmt.Println(err)
            break
        }
        fmt.Println(req)
    }
}

启动服务端,客户端发起请求:客户端每隔1s接收到一次客户端的响应信息我是客户端发来的信息,10s后接收到nil,服务端向客户端返回响应信息完成了

流式出入(双向流)

  • 双向流有两种形式:
    • 一种是客户端不断的给服务端传,服务端也不断的给客户端回;
    • 另一种是把channel接入,是即刻的,类似聊天室,客户端发一句,服务端回一句

代码示例

server.go

 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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package main

import (
    "context"
    "fmt"
    "goclass/pb_1/person"
    "google.golang.org/grpc"
    "net"
    "time"
)

//1.取出server
type personServer struct {
    person.UnimplementedPersonServiceServer
}

//2.挂载方法
func (*personServer) Search(ctx context.Context, req *person.PersonReq) (*person.PersonRes, error) {
    name := req.GetName()
    res := &person.PersonRes{Name: "我收到了" + name + "的信息"}
    return res, nil
}
func (*personServer) SearchIn(server person.PersonService_SearchInServer) error {
    for {
        //接收并打印请求信息
        req, err := server.Recv()
        fmt.Println(req)
        if err != nil {
            server.SendAndClose(&person.PersonRes{Name: "完成了"})
            break
        }
    }
    return nil
}
func (*personServer) SearchOut(req *person.PersonReq, server person.PersonService_SearchOutServer) error {
    name := req.Name
    i := 0
    for {
        if i > 10 {
            break
        }
        time.Sleep(1 * time.Second)
        i++
        server.Send(&person.PersonRes{Name: "我拿到了" + name})
    }
    return nil
}
func (*personServer) SearchIO(server person.PersonService_SearchIOServer) error {
    i := 0
    str := make(chan string)
    //流式接收
    go func() {
        for {
            i++
            req, _ := server.Recv()
            if i > 10 {
                str <- "结束"
                break
            }
            //保存到channel
            str <- req.Name
        }
    }()
    //流式发送
    for {
        //从channel中取出
        s := <-str
        if s == "结束" {
            server.Send(&person.PersonRes{Name: s})
            break
        }
        server.Send(&person.PersonRes{Name: s})
    }
    return nil
}

func main() {
    //3.注册服务
    //3.1.注册监听
    l, _ := net.Listen("tcp", ":8888")
    //3.2.注册一个server
    s := grpc.NewServer()
    person.RegisterPersonServiceServer(s, &personServer{})
    //4.创建监听
    s.Serve(l)
}

client.go

 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 "C"
import (
    "context"
    "fmt"
    "goclass/pb_1/person"
    "google.golang.org/grpc"
    "sync"
    "time"
)

func main() {
    //建立一个连接到服务器
    conn, _ := grpc.Dial("localhost:8888", grpc.WithInsecure())
    //最后要关闭连接
    defer conn.Close()
    //new一个客户端
    client := person.NewPersonServiceClient(conn)
    //调用client的方法
    c, _ := client.SearchIO(context.Background())
    //创建两个WaitGroup等待执行
    wg := sync.WaitGroup{}
    wg.Add(2)
    //流式发送
    go func() {
        for {
            time.Sleep(1 * time.Second)
            err := c.Send(&person.PersonReq{Name: "wyatt"})
            if err != nil {
                wg.Done()
                break
            }
        }

    }()
    //流式接收
    go func() {
        for {
            req, err := c.Recv()
            if err != nil {
                fmt.Println(err)
                wg.Done()
                break
            }
            fmt.Println(req)
        }
    }()
    wg.Wait()
}

启动服务端,客户端发起请求:客户端每隔1s发送一次信息Name: "wyatt",服务端接收到后返回该信息给客户端,客户端打印,10s后,服务端发送结束信息给客户端,客户端接收到后打印,双向流结束

GRPCGateway的简单应用

使用步骤:

  1. prerequisites:由于之前装过protoc-gen-go-grpcprotoc-gen-go,所以只需要装protoc-gen-go-grpc就行:
1
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
  1. Adding gRPC-Gateway annotations to an existing proto file:,先在src下新建pb_2目录,然后安装例程中import后的路径创建"google/api/annotations.proto"google/api/http.proto,最后添加依赖包,将https://github.com/googleapis/googleapis/blob/master/google/api/annotations.protohttps://github.com/googleapis/googleapis/blob/master/google/api/http.proto中代码复制到自己创建的对应文件中:

  2. pb_2目录下新建.\build.bat文件,添加gateway的生成语句:

1
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out . --go-grpc_opt paths=source_relative --grpc-gateway_out . --grpc-gateway_opt paths=source_relative ./person/person.proto
  1. /pb_2/person/person.proto
 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
syntax = "proto3";//声明语法版本 告诉编译器用proto3来解读

package person;//给当前proto分配包名称 这个和go的包不一样

option go_package = "goclass/pb_2/person;person";//包路径(从mod里的module下开始写);别名

import "google/api/annotations.proto";

message PersonReq{
    //声明格式:类型 key(下划线) = 唯一(标识)
    string name = 1;
    int32 age = 2;
}
message PersonRes{
    //声明格式:类型 key(下划线) = 唯一(标识)
    string name = 1;
    int32 age = 2;
}

service PersonService{
    //传统的 即刻响应
    rpc Search(PersonReq) returns (PersonRes){
    //加入对restful的描述
    option(google.api.http)={
        post:"/api/person",
        body:"*"
    	};
    };
}
  1. server.go
 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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package main

import (
    "context"
    "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    "goclass/pb_2/person"
    "google.golang.org/grpc"
    "net"
    "net/http"
    "sync"
)

//1.取出server
type personServer struct {
    person.UnimplementedPersonServiceServer
}

//2.挂载方法
func (*personServer) Search(ctx context.Context, req *person.PersonReq) (*person.PersonRes, error) {
    name := req.GetName()
    res := &person.PersonRes{Name: "我收到了" + name + "的信息,来自grpcGateWay"}
    return res, nil
}

func main() {
    wg := sync.WaitGroup{}
    wg.Add(2)
    go registerGateWay(&wg)
    go registerGRPC(&wg)
    wg.Wait()
}
func registerGateWay(wg *sync.WaitGroup) {
    //创建一个客户端
    conn, _ := grpc.DialContext(
        context.Background(),
        "127.0.0.1:8888",
        grpc.WithBlock(),
        grpc.WithInsecure(),
    )
    //创建一个Mux
    mux := runtime.NewServeMux() //一个对外开放的mux

    //创建http服务
    gwServer := &http.Server{
        Handler: mux,
        Addr:    ":8090",
    }

    //注册网关handler
    _ = person.RegisterPersonServiceHandler(context.Background(), mux, conn)

    //监听网关
    gwServer.ListenAndServe()
    wg.Done()
}
func registerGRPC(wg *sync.WaitGroup) {
    //3.注册服务
    //3.1.注册监听
    l, _ := net.Listen("tcp", ":8888")
    //3.2.注册一个server
    s := grpc.NewServer()
    person.RegisterPersonServiceServer(s, &personServer{})
    //4.创建监听
    s.Serve(l)
    wg.Done()
}
  1. pb_2目录下执行.\build.bat后,可以看到在person目录下生成person.pb.gw.go
  2. API访问结果:

最后更新于 Mar 04, 2023 13:56 UTC
Built with Hugo
Theme Stack designed by Jimmy