wxvirus wxvirus
首页
  • Go文章

    • Go语言学习
  • Rust

    • Rust学习
  • Java

    • 《Java》
  • Python文章

    • Python
  • PHP文章

    • PHP设计模式
  • 学习笔记

    • 《Git》
  • HTML
  • CSS
  • JS
  • 技术文档
  • GitHub技巧
  • 刷题
  • 博客搭建
  • 算法学习
  • 架构设计
  • 设计模式
  • 学习
  • 面试
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

无解的lifecycle

let today = new Beginning()
首页
  • Go文章

    • Go语言学习
  • Rust

    • Rust学习
  • Java

    • 《Java》
  • Python文章

    • Python
  • PHP文章

    • PHP设计模式
  • 学习笔记

    • 《Git》
  • HTML
  • CSS
  • JS
  • 技术文档
  • GitHub技巧
  • 刷题
  • 博客搭建
  • 算法学习
  • 架构设计
  • 设计模式
  • 学习
  • 面试
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • C&C++

  • PHP

  • Python

  • Go

    • go基础

    • go核心

      • 静态链接
      • 动态链接
      • ELF文件结构
      • runtime
      • Go的编译过程
      • Go程序是如何运行的
      • Go项目包管理方法
      • map的扩容
      • go协程调度机制
      • sync包的Pool
      • gRPC入门
        • 创建中间文件
        • 创建 gRPC 服务端并运行
        • 创建客户端调用
        • 使用证书验证
        • 使用 gRPC 写 HTTP 服务
        • 使用 gRPC 开发的 3 个步骤
        • 示例:第一步编写proto文件
        • 示例:第二步,生成代码【服务端】
        • 示例:第三步编写业务逻辑
        • 编写客户端
    • 网络编程

    • gowebsocket

    • gocasbin

    • K8S

    • rabbitmq

    • 框架相关

    • go-zero

    • kafka

    • rpc

    • 性能相关

  • microservice

  • rust

  • Java

  • 学习笔记

  • 后端
  • Go
  • go核心
wxvirus
2022-12-08

gRPC入门

# 入门 gRPC

protobuf 文档 (opens new window)

https://github.com/golang/protobuf (opens new window)

# 创建中间文件

使用proto3版本

➜  grpcpro tree -L 3
.
├── go.mod
├── go.sum
├── main.go
├── pbfiles
│   └── Prod.proto
└── services
    └── Prod.pb.go

1
2
3
4
5
6
7
8
9
10
syntax = "proto3";

package services;

// 当前所属目录的同级目录 services
option go_package = "../services";

message ProdRequest {
  int32 prod_id = 1;
}

message ProdResponse {
  int32 prod_stock = 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

生成go代码

➜  pbfiles protoc --go_out=../services Prod.proto
1

# 创建 gRPC 服务端并运行

syntax = "proto3";

package services;

option go_package = "../services";

message ProdRequest {
  int32 prod_id = 1;
}

message ProdResponse {
  int32 prod_stock = 1;
}

service ProdService {
  rpc GetProdStock (ProdRequest) returns (ProdResponse);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

添加服务代码之后,需要重新生成,这次需要加上plugins=gprc

➜  pbfiles protoc --go_out=plugins=grpc:../services Prod.proto

1
2

如果出现不能使用的情况,可能需要升级一下proto-gen-go

go get github.com/golang/protobuf/protoc-gen-go
1

Prod.pb.go代码中有一个接口有方法

// ProdServiceServer is the server API for ProdService service.
type ProdServiceServer interface {
	GetProdStock(context.Context, *ProdRequest) (*ProdResponse, error)
}
1
2
3
4

我们需要写一个自己的服务代码去实现这个功能。

services/ProdService.go

package services

import (
	"context"
)

type ProdService struct{}

func (p *ProdService) GetProdStock(ctx context.Context, req *ProdRequest) (*ProdResponse, error) {
	return &ProdResponse{ProdStock: 20}, nil
}

1
2
3
4
5
6
7
8
9
10
11
12

简单实现一个功能即可。

服务端启动

main.go

package main

import (
	"google.golang.org/grpc"
	"grpcpro/services"
	"net"
)

func main() {
	rpcServer := grpc.NewServer()
    // 注册服务
	services.RegisterProdServiceServer(rpcServer, new(services.ProdService))

	lis, _ := net.Listen("tcp", ":8081")

    // 使用 tcp 的方式
	err := rpcServer.Serve(lis)
	if err != nil {
		return
	}
}

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

# 创建客户端调用

我们将上面生成的Prod.pb.go代码复制一份到客户端代码里还是同样的services文件夹里,然后我们创建客户端来调用

main.go

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"grpccli/services"
	"log"
)

func main() {
	conn, err := grpc.Dial(":8081")
	if err != nil {
		log.Fatalln(err)
	}
	defer conn.Close()

	prodClient := services.NewProdServiceClient(conn)
	prodResp, err := prodClient.GetProdStock(context.Background(), &services.ProdRequest{
		ProdId: 20,
	})
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(prodResp.ProdStock)
}

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

我们是在本地运行的,所以没有HTTPS服务,需要加上一个函数,以前是使用WithInsecure(),但是现在弃用了,提示使用如下

// WithInsecure returns a DialOption which disables transport security for this
// ClientConn. Under the hood, it uses insecure.NewCredentials().
//
// Note that using this DialOption with per-RPC credentials (through
// WithCredentialsBundle or WithPerRPCCredentials) which require transport
// security is incompatible and will cause grpc.Dial() to fail.
//
// Deprecated: use WithTransportCredentials and insecure.NewCredentials()
// instead. Will be supported throughout 1.x.
func WithInsecure() DialOption {
	return newFuncDialOption(func(o *dialOptions) {
		o.copts.TransportCredentials = insecure.NewCredentials()
	})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

所以代码替换为

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"grpccli/services"
	"log"
)

func main() {
	conn, err := grpc.Dial(":8081", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalln(err)
	}
	defer conn.Close()

	prodClient := services.NewProdServiceClient(conn)
	prodResp, err := prodClient.GetProdStock(context.Background(), &services.ProdRequest{
		ProdId: 20,
	})
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(prodResp.ProdStock)
}

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

然后服务端代码和客户端代码都运行起来,查看是否返回对应的内容。

运行成功

# 使用证书验证

服务端

package main

import (
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"grpcpro/services"
	"log"
	"net"
)

func main() {

	crs, err := credentials.NewServerTLSFromFile("keys/xxx.crt", "keys/server_no_password.key")
	if err != nil {
		log.Fatalln(err)
	}

	rpcServer := grpc.NewServer(grpc.Creds(crs))
	services.RegisterProdServiceServer(rpcServer, new(services.ProdService))

	lis, _ := net.Listen("tcp", ":8081")

	rpcServer.Serve(lis)
}

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

这里的 crt 文件需要替换成真正的证书文件,以对应的 key 文件

客户端,只需要一个 crt 文件

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"grpccli/services"
	"log"
)

func main() {
	crs, err := credentials.NewClientTLSFromFile("keys/xxx.crt", "xxx.com")
	if err != nil {
		log.Fatalln(err)
	}
	conn, err := grpc.Dial(":8081", grpc.WithTransportCredentials(crs))
	if err != nil {
		log.Fatalln(err)
	}
	defer conn.Close()

	prodClient := services.NewProdServiceClient(conn)
	prodResp, err := prodClient.GetProdStock(context.Background(), &services.ProdRequest{
		ProdId: 20,
	})
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(prodResp.ProdStock)
}

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

xxx.com是你对应的网站哪个网站使用的证书。

# 使用 gRPC 写 HTTP 服务

package main

import (
	"google.golang.org/grpc"
	"grpcpro/services"
	"net/http"
)

func main() {
	rpcServer := grpc.NewServer()
	services.RegisterProdServiceServer(rpcServer, new(services.ProdService))

	//lis, _ := net.Listen("tcp", ":8081")
	//err := rpcServer.Serve(lis)
	//if err != nil {
	//	return
	//}

	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		rpcServer.ServeHTTP(w, r)
	})

	httpServer := &http.Server{
		Addr:    ":8081",
		Handler: mux,
	}
	httpServer.ListenAndServe()
}

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

此时你可以使用浏览器访问一下http://localhost:8081

会出现

gRPC requires HTTP/2
1

gRPC是使用的 HTTP2 协议是,使用 1.1 的方式是访问不了的。

这里如果打印请求头信息的话,出现的Content-Type则回事application/gRPC,协议是HTTP2

# 使用 gRPC 开发的 3 个步骤

  1. 编写protobuf文件
  2. 生成代码【包括服务端和客户端】
  3. 编写业务逻辑,会使用第二步生成的代码

# 示例:第一步编写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){}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 示例:第二步,生成代码【服务端】

在项目根目录下执行以下命令,根据hello.proto生成go源码文件

protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
pb/hello.proto
1
2
3
  • --go_out:指定生成的go源码文件到当前项目目录
  • --go_opt:使用的模式是使用的相对路径的模式,这里即相对于pb目录下
  • --go-grpc_out:生成的gRPC的源码文件目录
  • --go-grpc_opt:相对路径模式

:::caution 注意

如果你的终端不支持\,你就写成一行然后再进行赋值。

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative pb/hello.proto
1

:::

├── go.mod
└── pb
    ├── hello.pb.go
    ├── hello.proto
    └── hello_grpc.pb.go
1
2
3
4
5

# 示例:第三步编写业务逻辑

需要使用到生产的代码的时候,我们得先下载一下依赖

go mod tidy
1

我们下面的步骤就是编写业务代码,我们需要创建一个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
	}
}

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

# 编写客户端

再新建一个hello_client项目,同样也需要使用mod进行初始化

go mod init hello_client
1

为了便携效果,直接把服务端的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
1
2
3

编写客户端逻辑

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())
}

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


使用命令行参数解析来动态传参

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())
}

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
➜  hello_client ./hello_client -name 哈哈哈
2022/10/26 21:46:55 resp: hello 哈哈哈
2022/10/26 21:46:55 resp: hello 哈哈哈

1
2
3
4
编辑 (opens new window)
#gRPC
上次更新: 2022/12/08, 22:08:26
sync包的Pool
socket网络编程概述

← sync包的Pool socket网络编程概述→

最近更新
01
vue3配合vite初始化项目的一些配置
07-26
02
网盘系统开发学习
07-24
03
linux多进程
06-19
更多文章>
Theme by Vdoing | Copyright © 2021-2024 wxvirus 苏ICP备2021007210号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式