grpc使用

使用grpc和protobuf实现rpc通信的例子

创建工程

mkdir grpc_test
cd grpc_test
go mod init

//使用github的grpc替换gp的grpc  
go mod edit -replace=google.golang.org/grpc=github.com/grpc/grpc-go@latest
go mod edit -replace=golang.org/x/net=github.com/golang/net@latest

go mod tidy
go mod vendor
go build -mod=vendor

科学上网可以不用上面的replace,直接设置如下环境:
export GOPROXY=https://goproxy.io 或者 set GOPROXY=https://goproxy.io

工程准备

grpc_test
    /proto/ 
        /search.proto   
    /client/    
        /client.go  
    /server/    
        /server.go  

生成proto

编写proto

syntax = "proto3";

package proto;

service SearchService {
    rpc Search(SearchRequest) returns (SearchResponse) {}
}

message SearchRequest {
    string request = 1;
}

message SearchResponse {
    string response = 1;
}

定义中包含了服务接口的定义.

protobuf环境

首先下载protoc放入path
https://github.com/protocolbuffers/protobuf/releases

go get -u github.com/golang/protobuf/protoc-gen-go //下载proto go插件
export PATH=$PATH:$GOPATH/bin  //protoc-gen-go 添加到path

生成

确保proto和protoc-gen-go可用,protoc参数中–go_out会自动加载protoc-gen-go

protoc --go_out=plugins=grpc: 生成目录 proto文件或者目录

protoc --go_out=plugins=grpc:. *.proto

会生成search.pb.go

编写server

package main

import (
	"context"
	"log"
	"net"

	pb "grpctest/proto"

	"google.golang.org/grpc"
)

type SearchService struct{}

func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) {
	return &pb.SearchResponse{Response: r.GetRequest() + " Server"}, nil
}

const PORT = "9001"

func main() {
	server := grpc.NewServer() //创建 gRPC Server对象

	//将 SearchService(其包含需要被调用的服务端接口)注册到gRPC Server 的内部注册中心
	//这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理
	pb.RegisterSearchServiceServer(server, &SearchService{})

	lis, err := net.Listen("tcp", ":"+PORT) //创建 Listen,监听 TCP 端口
	if err != nil {
		log.Fatalf("net.Listen err: %v", err)
	}

	//gRPC Server开始 lis.Accept,直到 Stop 或 GracefulStop
	server.Serve(lis)
}

编写client

package main

import (
	"context"
	"log"

	"google.golang.org/grpc"

	pb "grpctest/proto"
)

const PORT = "9001"

func main() {
	//连接grpc server
	conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("grpc.Dial err: %v", err)
	}
	defer conn.Close()

	//创建 SearchService 的客户端对象
	client := pb.NewSearchServiceClient(conn)

	//发送 RPC 请求,等待同步响应,得到回调后返回响应结果
	resp, err := client.Search(context.Background(), &pb.SearchRequest{
		Request: "gRPC",
	})
	if err != nil {
		log.Fatalf("client.Search err: %v", err)
	}

	log.Printf("resp: %s", resp.GetResponse())
}

数据流模式RPC

在上面的例子中展示的一元rpc也就是简单rpc的模式。gPrc还有流模式的rpc. 分为服务端流rpc\客户端流rpc\双向流迷失rpc

服务端流RPC

在服务端流模式的RPC实现中,服务端得到客户端请求后,处理结束返回一个数据应答流。在发送完所有的客户端请求的应答数据后,服务端的状态详情和可选的跟踪元数据发送给客户端

服务接口定义

通过stream修饰的方式表示该接口调用时,服务端会以数据流的形式将数据返回给客户端

//订单服务service定义
service OrderService {
    rpc GetOrderInfos (OrderRequest) returns (stream OrderInfo) {}; //服务端流模式
}
生成代码变化

protoc --go_out=plugins=grpc:. *.proto

在自动生成的go代码程序当中,每一个流模式对应的服务接口,都会自动生成对应的单独的client和server程序,以及对应的结构体实现。

服务端生成代码

流模式下,服务接口的服务端提供Send方法,将数据以流的形式进行发送

type OrderService_GetOrderInfosServer interface {
    Send(*OrderInfo) error
    grpc.ServerStream
}

type orderServiceGetOrderInfosServer struct {
    grpc.ServerStream
}

func (x *orderServiceGetOrderInfosServer) Send(m *OrderInfo) error {
    return x.ServerStream.SendMsg(m)
}
客户端生成代码

流模式下,服务接口的客户端提供Recv()方法接收服务端发送的流数据

type OrderService_GetOrderInfosClient interface {
    Recv() (*OrderInfo, error)
    grpc.ClientStream
}

type orderServiceGetOrderInfosClient struct {
    grpc.ClientStream
}

func (x *orderServiceGetOrderInfosClient) Recv() (*OrderInfo, error) {
    m := new(OrderInfo)
    if err := x.ClientStream.RecvMsg(m); err != nil {
        return nil, err
    }
    return m, nil
}
服务端实现

因为是流模式开发,服务端将数据以流的形式进行发送,因此,该方法的第二个参数类型为OrderService_GetOrderInfosServer,该参数类型是一个接口,其中包含Send方法,允许发送流数据。Send方法的具体实现在编译好的pb.go文件中,进一步调用grpc.SeverStream.SendMsg方法
服务端注册模式和一元rpc是没区别的

//订单服务实现
type OrderServiceImpl struct {
}

//获取订单信息s
func (os *OrderServiceImpl) GetOrderInfos(request *message.OrderRequest, stream message.OrderService_GetOrderInfosServer) error {
    fmt.Println(" 服务端流 RPC 模式")

    orderMap := map[string]message.OrderInfo{
        "201907300001": message.OrderInfo{OrderId: "201907300001", OrderName: "衣服", OrderStatus: "已付款"},
        "201907310001": message.OrderInfo{OrderId: "201907310001", OrderName: "零食", OrderStatus: "已付款"},
        "201907310002": message.OrderInfo{OrderId: "201907310002", OrderName: "食品", OrderStatus: "未付款"},
    }
    for id, info := range orderMap {
        if (time.Now().Unix() >= request.TimeStamp) {
            fmt.Println("订单序列号ID:", id)
            fmt.Println("订单详情:", info)
            //通过流模式发送给客户端
            stream.Send(&info)
        }
    }
    return nil
}
客户端实现

服务端使用Send方法将数据以流的形式进行发送,客户端可以使用Recv()方法接收流数据,因为数据流失源源不断的,因此使用for无限循环实现数据流的读取,当读取到io.EOF时,表示流数据结束.

for {
        orderInfo, err := orderInfoClient.Recv()
        if err == io.EOF {
            fmt.Println("读取结束")
            return
        }
        if err != nil {
            panic(err.Error())
        }
        fmt.Println("读取到的信息:", orderInfo)
    }

客户端流RPC

服务端以数据流的形式返回数据的形式。对应的,也存在客户端以流的形式发送请求数据的形式。

服务接口定义

与服务端同理,客户端流模式的RPC服务声明格式,就是使用stream修饰服务接口的接收参数

//订单服务service定义
service OrderService {
    rpc AddOrderList (stream OrderRequest) returns (OrderInfo) {}; //客户端流模式
}
生成代码的差异

SendAndClose和Recv方法是客户端流模式下的服务端对象所拥有的方法
Send和CloseAndRecv是客户端流模式下的客户端对象所拥有的方法。

双向流模式

上文已经讲过了服务端流模式和客户端流模式。如果将客户端和服务端两种流模式结合起来,就是第三种模式,双向流模式。即客户端发送数据的时候以流数据发送,服务端返回数据也以流的形式进行发送,因此称之为双向流模式。

服务接口定义
//订单服务service定义
service OrderService {
    rpc GetOrderInfos (stream OrderRequest) returns (stream OrderInfo) {}; //双向流模式
}
生成代码的差异

服务端和客户端都实现了send 和 recv方法用来接收和发送流式的数据

TLS验证和Token认证

gRPC中默认支持两种授权方式,分别是:SSL/TLS认证方式、基于Token的认证方式

SSL/TLS认证方式

SL全称是Secure Sockets Layer,又被称之为安全套接字层,是一种标准安全协议,用于在通信过程中建立客户端与服务器之间的加密链接。
TLS的全称是Transport Layer Security,TLS是SSL的升级版。在使用的过程中,往往习惯于将SSL和TLS组合在一起写作SSL/TLS。
简而言之,SSL/TLS是一种用于网络通信中加密的安全协议。

使用SSL/TLS协议对通信连接进行安全加密,是通过非对称加密的方式来实现的。所谓非对称加密方式又称之为公钥加密,密钥对由公钥和私钥两种密钥组成。私钥和公钥成对存在,先生成私钥,通过私钥生成对应的公钥。公钥可以公开,私钥进行妥善保存。

在加密过程中:客户端想要向服务器发起链接,首先会先向服务端请求要加密的公钥。获取到公钥后客户端使用公钥将信息进行加密,服务端接收到加密信息,使用私钥对信息进行解密并进行其他后续处理,完成整个信道加密并实现数据传输的过程。 公钥加密私钥解密,非对称加密算法.

生成证书
openssl ecparam -genkey -name secp384r1 -out server.key
openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650
开启TLS认证的服务端和客户端连接代码
//TLS认证
creds, err := credentials.NewServerTLSFromFile("./keys/server.pem","./keys/server.key")
if err != nil {
    grpclog.Fatal("加载在证书文件失败", err)
}

//实例化grpc server, 开启TLS认证
server := grpc.NewServer(grpc.Creds(creds))
    
//TLS连接
creds, err := credentials.NewClientTLSFromFile("./keys/server.pem", "go-grpc-example")
if err != nil {
    panic(err.Error())
}
//1、Dail连接
conn, err := grpc.Dial("localhost:8092", grpc.WithTransportCredentials(creds))
if err != nil {
    panic(err.Error())
}

基于Token认证方式

在web应用的开发过程中会使用另外一种认证方式进行身份验证,那就是:Token认证。基于Token的身份验证是无状态,不需要将用户信息服务存在服务器或者session中.
基于Token认证的身份验证主要过程是:客户端在发送请求前,首先向服务器发起请求,服务器返回一个生成的token给客户端。客户端将token保存下来,用于后续每次请求时,携带着token参数。服务端在进行处理请求之前,会首先对token进行验证,只有token验证成功了,才会处理并返回相关的数据。

自定义Token
grpc.WithPerRPCCredentials(PerRPCCredentials)

type PerRPCCredentials interface {
    //组织token信息
    GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
    //设置是否基于tls认证进行安全传输
    RequireTransportSecurity() bool 
}

自定义token只需要实现PerRPCCredentials接口就可以了。

在客户端进行连接时,我们将自定义的token认证信息作为参数进行传入
//token认证
type TokenAuthentication struct {
    AppKey    string
    AppSecret string
}

//组织token信息
func (ta *TokenAuthentication) RequestMetaData(ctx context.Context, uri ...string) (map[string]string, error) {
    return map[string]string{
        "appid":    ta.AppKey,
        "appkey": ta.AppSecret,
    }, nil
}

//是否基于TLS认证进行安全传输
func (a *TokenAuthentication) RequireTransportSecurity() bool {
    return true
}

auth := TokenAuthentication{
        AppKey:    "hello",
        AppSecret: "20190812",
}
conn, err := grpc.Dial("localhost:8093", grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&auth))
if err != nil {
    panic(err.Error())
}

服务端token校验

在服务端的调用方法中实现对token请求参数的判断,可以通过metadata获取token认证信息

func (mm *MathManager) AddMethod(ctx context.Context, request *message.RequestArgs) (response *message.Response, err error) {

    //通过metadata
    md, exist := metadata.FromIncomingContext(ctx)
    if !exist {
        return nil, status.Errorf(codes.Unauthenticated, "无Token认证信息")
    }

    var appKey string
    var appSecret string

    if key, ok := md["appid"]; ok {
        appKey = key[0]
    }

    if secret, ok := md["appkey"]; ok {
        appSecret = secret[0]
    }
}

拦截器的使用

在服务端的方法中,每个方法都要进行token的判断。程序效率太低,可以优化一下处理逻辑,在调用服务端的具体方法之前,先进行拦截,并进行token验证判断,这种方式称之为拦截器处理。除了此处的token验证判断处理以外,还可以进行日志处理等.

Interceptor

在grpc中编程实现中,可以在NewSever时添加拦截器设置,grpc框架中可以通过UnaryInterceptor方法设置自定义的拦截器

grpc.UnaryInterceptor(UnaryServerInterceptor)


type UnaryServerInterceptor func(ctx context.Context, 
                                req interface{}, 
                                info *UnaryServerInfo, 
                                handler UnaryHandler) (resp interface{}, err error)

自定义拦截器

func TokenInterceptor(ctx context.Context, 
                    req interface{}, 
                    info *grpc.UnaryServerInfo, 
                    handler grpc.UnaryHandler) (resp interface{}, err error) {

    //通过metadata
    md, exist := metadata.FromIncomingContext(ctx)
    if !exist {
        return nil, status.Errorf(codes.Unauthenticated, "无Token认证信息")
    }

    var appKey string
    var appSecret string
    if key, ok := md["appid"]; ok {
        appKey = key[0]
    }
    if secret, ok := md["appkey"]; ok {
        appSecret = secret[0]
    }

    if appKey != "hello" || appSecret != "20190812" {
        return nil, status.Errorf(codes.Unauthenticated, "Token 不合法")
    }
    //通过token验证,继续处理请求
    return handler(ctx, req)
}

在自定义的TokenInterceptor方法定义中,和之前在服务的方法调用的验证逻辑一致,从metadata中取出请求头中携带的token认证信息,并进行验证是否正确。如果token验证通过,则继续处理请求后续逻辑,后续继续处理可以由grpc.UnaryHandler进行处理

注册拦截器

server:=grpc.NewServer(grpc.Creds(creds),grpc.UnaryInterceptor(TokenInterceptor))
<think>嗯,我现在要写一篇关于gRPC使用攻略的介绍。用户需要结构清晰,分步骤解决问题,同时内容真实可靠。首先,我应该先回忆一下自己对gRPC的了解,然后整理出主要的点,确保不遗漏关键内容。 首先,gRPC是什么?它是一个高性能、开源的RPC框架,由Google开发,基于HTTP/2和Protocol Buffers。那RPC又是什么呢?远程过程调用,允许像调用本地函数一样调用远程服务。这点需要解释清楚,因为可能有些读者不熟悉这个概念。 接下来,gRPC的核心特性。比如基于HTTP/2,支持双向流、多路复用;使用Protocol Buffers作为IDL,高效序列化;支持多种编程语言;还有四种通信模式,这个要详细说明。可能用户会关心这些特性带来的好处,比如性能提升,跨语言支持等。 然后是使用步骤。第一步应该是定义服务接口,也就是编写.proto文件。这部分需要详细说明语法,比如如何定义service、rpc方法、消息类型。比如一个简单的例子,定义一个Hello服务,里面有个SayHello的rpc方法,接收HelloRequest,返回HelloResponse。这里要注意语法正确,比如message的结构,字段类型和编号。 第二步是生成代码,使用protoc编译器配合gRPC插件生成对应语言的代码。需要提到常用的语言,比如Go、Java、Python等,以及如何安装protoc工具,生成命令的例子,比如protoc --go_out=. --go-grpc_out=. *.proto。 第三步是实现服务端。需要举一个例子,比如用Go语言实现服务接口,创建gRPC服务器,注册服务,监听端口,启动服务器。代码示例可能对用户有帮助,但要注意不要出错,同时用中文解释每一步的作用。 第四步是实现客户端。同样用例子说明如何创建连接,创建客户端实例,调用远程方法,处理响应。这里要强调客户端如何像调用本地方法一样使用远程服务。 第五步是运行和测试,确保服务端和客户端能正常通信,可以补充测试方法,比如使用grpcurl工具或者编写单元测试。 接下来要讲通信模式,四种类型:简单RPC、服务端流式、客户端流式、双向流式。每种模式的应用场景和示例,比如简单RPC适合一次性请求响应,流式适合大数据传输或实时通信。这部分可能需要用不同的.proto定义来展示,比如在rpc方法中使用stream关键字。 然后是关键技术点:Protocol Buffers的高效序列化,HTTP/2的优势,拦截器的作用,比如认证、日志、监控,还有超时和错误处理的重要性。需要详细说明每个技术点如何应用,比如设置超时时间,处理状态码。 最后是注意事项,比如版本兼容性,.proto文件的维护,性能优化方法,比如连接复用、压缩,以及安全方面的TLS加密和身份验证。 在组织内容时,需要确保结构清晰,分步骤讲解,每个部分有必要的示例和解释。同时,注意使用中文,避免专业术语过于晦涩,必要时进行解释。可能还需要检查是否有遗漏的部分,比如负载均衡、服务发现等高级话题,但根据用户的要求,可能基础攻略不需要深入这些,但可以简要提及扩展方向。 还要注意LaTeX格式的问题,用户提到行内公式用$...$,独立公式用$$...$$,但在这个主题中可能数学公式不多,但如果有涉及性能数据或公式,需要正确应用格式。不过gRPC攻略可能较少涉及数学表达式,所以这部分可能用不上,但需要留意用户可能的隐藏要求。 最后总结一下,确保内容覆盖了gRPC的核心概念、使用步骤、关键技术和注意事项,结构层次分明,帮助用户从零开始使用gRPC,并解决可能遇到的问题。</think>以下是一份结构清晰的gRPC使用攻略,分步骤解析核心用法及注意事项: --- ### 一、gRPC基础概念 1. **定义** gRPCGoogle开源的高性能RPC框架,基于**HTTP/2协议**和**Protocol Buffers**(Protobuf)实现,支持跨语言调用。 2. **核心特性** - 多语言支持(如Go、Java、Python等) - 双向流式通信 - 高效的序列化(Protobuf) - 内置负载均衡、重试、超时机制 --- ### 二、使用步骤详解 #### 步骤1:定义服务接口(.proto文件) ```protobuf syntax = "proto3"; service Greeter { rpc SayHello (HelloRequest) returns (HelloResponse) {} } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; } ``` - **关键语法** - `service`定义服务,`rpc`定义方法 - `message`定义请求/响应结构 - 字段编号(如`=1`)需唯一且不可重复 #### 步骤2:生成代码 安装编译器`protoc`和对应语言插件(以Go为例): ```bash protoc --go_out=. --go-grpc_out=. *.proto ``` 生成代码包含: - 客户端存根(Stub) - 服务端接口 #### 步骤3:实现服务端 ```go type server struct { pb.UnimplementedGreeterServer } func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) { return &pb.HelloResponse{Message: "Hello " + req.Name}, nil } // 启动服务 lis, _ := net.Listen("tcp", ":50051") s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) s.Serve(lis) ``` #### 步骤4:实现客户端 ```go conn, _ := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) client := pb.NewGreeterClient(conn) resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{Name: "World"}) fmt.Println(resp.Message) // 输出:Hello World ``` #### 步骤5:运行与测试 - 启动服务端:`go run server.go` - 运行客户端验证结果 - 推荐工具:`grpcurl`(命令行测试)、BloomRPC(GUI工具) --- ### 三、4种通信模式 1. **简单RPC(Unary)** 一请求一响应,示例见上文。 2. **服务端流式(Server Streaming)** ```protobuf rpc StreamData (Request) returns (stream Response) {} ``` 3. **客户端流式(Client Streaming)** ```protobuf rpc UploadData (stream Request) returns (Response) {} ``` 4. **双向流式(Bidirectional Streaming)** ```protobuf rpc Chat (stream Message) returns (stream Message) {} ``` --- ### 四、关键技术点 1. **Protocol Buffers优化** - 使用`repeated`定义数组 - 通过`oneof`实现联合类型 2. **HTTP/2优势** - 多路复用降低延迟 - 头部压缩减少带宽占用 3. **拦截器(Interceptor)** - 客户端拦截器:实现认证、日志 - 服务端拦截器:限流、监控 4. **错误处理** - 使用`status`包返回错误码: ```go return nil, status.Error(codes.NotFound, "data not found") ``` --- ### 五、注意事项 1. **版本兼容性** 确保`protoc`编译器与gRPC库版本匹配。 2. **性能优化** - 复用gRPC连接(避免频繁创建) - 启用压缩:`grpc.UseCompressor("gzip")` 3. **安全配置** - 服务端启用TLS: ```go creds := credentials.NewServerTLSFromCert(cert) s := grpc.NewServer(grpc.Creds(creds)) ``` --- ### 六、扩展场景 - **服务发现**:集成Consul/Etcd - **负载均衡**:客户端权重轮询 - **网关代理**:搭配gRPC-Gateway暴露HTTP接口 --- 通过以上步骤和技巧,可快速掌握gRPC的核心用法。建议从简单RPC入手,逐步尝试流式通信和高级特性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值