【gRPC】服务端与客户端搭建

  • ~4.94K 字
  1. 1. Protobuf文件实现
  2. 2. 实现一个server
  3. 3. 实现一个Client
    1. 3.1. 整体链路
      1. 3.1.1. 入口
      2. 3.1.2. controller
      3. 3.1.3. 客户端

Protobuf文件实现

若需要采用grpc相关,需要在proto文件中以service定义,例:

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";
package auth;

import "user.proto";
option go_package = "./auth;auth";

// 定义服务
service AuthService {
rpc Login(LoginRequest) returns (LoginResponse);
rpc Register(RegisterRequest) returns (RegisterResponse);
}

message LoginRequest {
string username = 1;
string password = 2;
}

message LoginResponse {
string token = 1;
user.User user = 2;
}

message RegisterRequest {
string username = 1;
string password = 2;
string email = 3;
}

message registerresponse {}

使用编译命令编译后,除了普通的.pb.go文件,还会生成xx_grpc.pb.go文件,里面实现了客户端和服务端需要的一些方法

1
2
protoc -I. -I/usr/include --go_out=. --go_opt=module=grpc-study --go-grpc_out=. --go-grpc_opt=module=grpc-study ./au
th.proto

实现一个server

首先在/internal/grpc下创建一个server.go

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

import (
"fmt"
"net"

"google.golang.org/grpc"
)

func StartGrpcServer() {
ln, err := net.Listen("tc p", ":8000")
if err != nil {
panic(err)
}
s := grpc.NewServer()
err = s.Serve(ln)
if err != nil {
fmt.Println(err)
return
}
}

接下来需要实现方法,在service文件夹下创建auth.go,实现一个登录方法

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

import (
"context"
userProto "grpc-study/auth"
"grpc-study/model"
)

type AuthService struct {
}

func NewAuthService() *AuthService {
return &AuthService{}
}

func (s *AuthService) Login(ctx context.Context, req *userProto.LoginRequest) (*model.UserModel, error) {
return &model.UserModel{
Id: 1,
Username: req.Username,
Password: req.Password,
Email: "",
},nil
}

Model层实现如下:

1
2
3
4
5
6
7
8
9
package model

type UserModel struct {
Id int64
Username string
Password string
Email string
}

接下来需要将业务的注册到服务端,需要实现注册方法,ctrl建立文件auth.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
package ctrl

import (
"context"
userProto "grpc-study/auth"
"grpc-study/service"
userModelProto "grpc-study/user"
"log"
)
type AuthController struct {
userProto.UnimplementedAuthServiceServer
}

func validateLoginRequest(req *userProto.LoginRequest) error {
return nil
}

func (c *AuthController) Login(ctx context.Context, req *userProto.LoginRequest) (*userProto.LoginResponse, error) {
// 验证参数
if err := validateLoginRequest(req); err != nil {
return nil, err
}

// 业务逻辑
log.Println("Login request received:", req.Username, req.Password)
userModel, err := service.NewAuthService().Login(ctx, req)
if err != nil {
return nil, err
}

// 组装响应数据
resp := &userProto.LoginResponse{
Token: "123",
User: &userModelProto.User{
Id: userModel.Id,
},
}
// 返回参数
return resp, nil
}


func (c *AuthController) Register(ctx context.Context, req *userProto.RegisterRequest) (*userProto.RegisterResponse, error) {
return &userProto.RegisterResponse{}, nil
}

RegisterAuthServiceServer用于方法的内部注册,即将我们自己定义的AuthController交给gRPC服务器,让将来发入的auth服务请求交给这个实例处理,其中AuthController是我们自己定义的,实现了AuthServiceServer接口要求的方法的实例

最后在server端添加注册:

1
2
3
4
// 注册服务到服务端中
authCtrl := &ctrl.AuthController{}
s := grpc.NewServer()
userProto.RegisterAuthServiceServer(s, authCtrl)

实现一个Client

开一个文件夹放客户端,/authclient下的main.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
package main

import (
"context"
userProto "grpc-study/auth"
"fmt"
"log"
"sync"
"sync/atomic"
"google.golang.org/grpc"
)

func main() {
cc := NewUserClientPool("localhost:8000", 5)
resp,err := cc.GetClient().Login(context.Background(), &userProto.LoginRequest{
Username: "admin",
Password: "admin",
})
if err != nil {
log.Fatal(err)
}
fmt.Println("Login response received:", resp.Token, resp.User.Id)
}

// 多路复用
type userClientPool struct {
clients []userProto.AuthServiceClient
mu sync.Mutex
index int64

}

func NewUserClientPool(addr string, size int) *userClientPool {
cc, err := grpc.Dial(addr, grpc.WithInsecure())
if err != nil {
panic(err)
}
var clients []userProto.AuthServiceClient
for i := 0; i < size; i++ {
clients = append(clients, userProto.NewAuthServiceClient(cc))
}
return &userClientPool{
clients: clients,
index: 0,
}
}
func (p *userClientPool) GetClient() userProto.AuthServiceClient {
// 新增index
index := atomic.AddInt64(&p.index, 1)
return p.clients[int(index)%len(p.clients)]
}
  • 这里采用了多路复用技术,即开一个用户池,可以开启多个连接
  • 这里实现了负载均衡,每次调用后悔从池里挑一个client发送请求
  • 请求再发送给服务端,服务端执行业务逻辑,返回响应

整体链路

上面实现的是一个标准的gRPC调用闭环:

客户端生成stub发起RPC→

服务端启动时把controller注册到gRPC的服务端→

收到请求后由controller调用业务层→

把业务结果组装为proto响应返回

入口

服务端主程序仅仅调用了server.go的开启服务端函数,这个函数通过net.Listen()监听tcp,创建gRPCserver,再把AuthController通过通过RegisterAuthServiceServer注册进去

这样,proto里定义的auth服务就和本地实现的auth业务逻辑绑定起来了,服务器知道如何处理rpc请求

controller

请求进来以后,会落到ctrl/auth.go,进入controller层

先进行参数校验,再调用方法AuthService.Login,实现后返回内部模型model.UserModel,ctrl再把返回的数据转换成proto的数据类型并组装成响应返回

因此service层只管业务数据,ctrl层负责把内部模型翻译为对外rpc协议

客户端

客户端在main.go中先使用grpc.Dial()建立连接,通过NewUserClientPool创建用户池,并从中GetClient()获取一个用户创建客户端stub

后面调用方法Login()会直接把你的LoginRequest序列化后发送到服务端,服务端处理后再把LoginResponse反序列化后返回客户端,拿到想要的东西

赞助喵
非常感谢您的喜欢!
赞助喵