社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
GRPC框架
源代码:https://gitee.com/g_night/grpc-demo
go get github.com/golang/protobuf/proto
go get google.golang.org/grpc
go get github.com/golang/protobuf/protoc-gen-go
go get google.golang.org/protobuf/reflect/protoreflect
go get google.golang.org/protobuf/runtime/protoimpl
protoc-gen-go.exe
、protoc.exe
,在源代码的目录中有个protobuf工具
,可以将这些可执行文件放到GOPATH/bin下创建 hello.proto
:
syntax = "proto3";
package service;
// 用于指定golang package的名字,新版本要求必须包含 /
option go_package = "service/";
message HelloRequest {
int64 id = 1;
}
message HelloResponse {
string name = 1;
}
service HelloService {
rpc SayHello(HelloRequest) returns (HelloResponse);
}
运行指令,生成hello.pb.go
protoc --go_out=plugins=grpc:../ hello.proto
# 规则是: --go_out=目录 proto文件
# 注意:这里需要添加 plugins=grpc: 这个用于指定使用的服务
创建文件hello_service.go
,编写核心服务:
package service
import (
"context"
"strconv"
)
// hello服务具体实现
type HelloService struct{}
// 方法名&参数 --> 就在 hello.pb.go 的 HelloServiceServer
func (server *HelloService) SayHello(ctx context.Context, req *HelloRequest) (*HelloResponse, error) {
number := strconv.Itoa(int(req.Id))
resp := &HelloResponse{Name: "hello word no." + number}
return resp, nil
}
创建文件server.go
package main
import (
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"grpc-demo/service"
"net"
)
// 具体server代码
func main() {
// 创建新服务
server := grpc.NewServer()
// 注册服务
service.RegisterHelloServiceServer(server, &service.HelloService{})
// 设置端口监听服务
listen, err := net.Listen("tcp", "127.0.0.1:9091")
if err != nil {
grpclog.Fatalf("Failed to listen: %v", err)
}
server.Serve(listen)
}
创建文件client_test.go
:
package main
// 客户端示例
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"grpc-demo/service"
"testing"
)
func TestClient(t *testing.T) {
// 连接 127.0.0.1:9091,地址要一致
conn, err := grpc.Dial("127.0.0.1:9091", grpc.WithInsecure())
if err != nil {
grpclog.Fatalln(err)
}
defer conn.Close()
// 初始化客户端
c := service.NewHelloServiceClient(conn)
// 构造请求
req := &service.HelloRequest{Id: 1}
// 调用服务
resp, err := c.SayHello(context.Background(), req)
// 服务调用失败
if err != nil {
grpclog.Fatalln(err)
}
fmt.Println("resp.Name = ", resp.Name)
}
首先运行server.go
的main
方法,确保服务开启。然后运行client_test.go
,就能看到显示:
=== RUN TestClient
resp.Name = hello word no.1 # 返回预期结果
--- PASS: TestClient (0.02s)
目录结构(源代码中位于demo1-helloword
文件夹):
步骤总结:
编写中间文件:xxx.proto
生成代码:protoc --go_out=plugins=grpc:../ xxx.proto
编写服务核心代码,声明对象,实现方法接口(Name):
func (s *XXX) Name(ctx context.Context, req *XXXRequest) (*XXXResponse, error)
运行服务:
// 创建新服务
server := grpc.NewServer()
// 注册服务
service.RegisterXXXServer(server, &service.XXXService{})
// 设置端口监听服务
listen, err := net.Listen("tcp", "127.0.0.1:9091")
if err != nil {
grpclog.Fatalf("Failed to listen: %v", err)
}
server.Serve(listen)
我们在demo1-helloword
的基础上加入token
校验,项目文件在源代码的demo2-token
。
引入认证包:
go get google.golang.org/grpc/credentials
客户端加入token
信息:
编写customCredential
自定义认证,实现PerRPCCredentials
接口的两个方法:
GetRequestMetadata(ctx context.Context, uri …string) (map[string]string, error)
RequireTransportSecurity() bool
package main
// 客户端示例
import (
"context"
"demo2-token/service"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"testing"
)
// customCredential 自定义认证
type customCredential struct{}
// GetRequestMetadata 实现自定义认证接口
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"token": "real_token",
}, nil
}
// RequireTransportSecurity 自定义认证是否开启TLS
func (c customCredential) RequireTransportSecurity() bool {
return false
}
func TestClient(t *testing.T) {
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
normalClient(opts)
}
// 常规请求操作
func normalClient(opts []grpc.DialOption) {
// 连接 127.0.0.1:9091,地址要一致
// 加入 token
conn, err := grpc.Dial("127.0.0.1:9091", opts...)
if err != nil {
grpclog.Fatalln(err)
}
defer conn.Close()
// 初始化客户端
c := service.NewHelloServiceClient(conn)
// 构造请求
req := &service.HelloRequest{Id: 1}
// 调用服务
resp, err := c.SayHello(context.Background(), req)
// 服务调用失败
if err != nil {
grpclog.Fatalln(err)
}
fmt.Println("resp.Name = ", resp.Name)
}
package main
// 客户端示例
import (
"context"
"demo2-token/service"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"testing"
)
// customCredential 自定义认证
type customCredential struct{}
// GetRequestMetadata 实现自定义认证接口
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"token": "real_token",
}, nil
}
// RequireTransportSecurity 自定义认证是否开启TLS
func (c customCredential) RequireTransportSecurity() bool {
return false
}
func TestClient(t *testing.T) {
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
normalClient(opts)
}
// 常规请求操作
func normalClient(opts []grpc.DialOption) {
// 连接 127.0.0.1:9091,地址要一致
// 加入token
conn, err := grpc.Dial("127.0.0.1:9091", opts...)
if err != nil {
grpclog.Fatalln(err)
}
defer conn.Close()
// 初始化客户端
c := service.NewHelloServiceClient(conn)
// 构造请求
req := &service.HelloRequest{Id: 1}
// 调用服务
resp, err := c.SayHello(context.Background(), req)
// 服务调用失败
if err != nil {
grpclog.Fatalln(err)
}
fmt.Println("resp.Name = ", resp.Name)
}
本文以 “proto3” 为依据!
文件以.proto做为文件后缀,除结构定义外的语句以分号结尾
结构定义可以包含:message、service、enum
末尾的数字表示在序列化数组里面的顺序。可以定义为任何数字(不能重复),不需要总是从1或者0开始
Service、Rpc方法名、Message命名采用驼峰命名方式(首字母大写),字段命名采用 小写字母 + 下划线 分隔方式
Enum类型命名采用 驼峰命名 ,而字段命名采用 大写字母 + 下划线 分隔方式
message HelloRequest {
required string name = 1;
}
enum MyEnums {
FIRST_VALUE = 1;
}
Protobuf定义了一套基本数据类型,对应规则:
.proto | C++ | Java | Go |
---|---|---|---|
double | double | double | float64 |
float | float | float | float32 |
int32 | int32 | int | int32 |
int64 | int64 | long | int64 |
uint32 | uint32 | int | uint32 |
uint64 | uint64 | long | uint64 |
sint32 | int32 | int | int32 |
sint64 | int64 | long | int64 |
fixed32 | uint32 | int | uint32 |
fixed64 | uint64 | long | uint64 |
sfixed32 | int32 | int | int32 |
sfixed64 | int64 | long | int64 |
bool | bool | boolean | bool |
string | string | String | string |
bytes | string | ByteString | []byte |
message
就相当于结构体,一般定义请求体、响应体。
字段修饰符(其实除非是repeated,否则不用写了):
声明细节:
声明的结构体可以嵌套使用:
message SearchResponse {
Result result = 1;
}
message Result {
string url = 1;
string title = 2;
}
如果是内部声明的message类型,则只能内部直接使用
message SearchResponse {
message Result {
string url = 1;
string title = 2;
}
Result result = 1;
}
message SearchRequest {
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
}
Corpus corpus = 1;
}
定义RPC服务接口,protocol buffer编译器
将会根据所选择的不同语言生成服务接口代码
service HelloService {
rpc Name(XXXRequest) returns (XXXResponse);
}
如果你希望创建一个关联映射,protocol buffer提供了一种快捷的语法:
map<key_type, value_type> map_field = N;
其中key_type
可以是任意Integer或者string类型(所以,除了floating和bytes的任意标量类型都是可以的)value_type
可以是任意类型。
例如,如果你希望创建一个project的映射,每个Projecct
使用一个string作为key,你可以像下面这样定义:
message SearchRequest {
map<string, string> mp = 3;
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!