【go微服务】如何快速掌握grpc开发

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑

🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。

🏆《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏: Go语言微服务
景天的主页: 景天科技苑

文章目录

grpc框架

GRPC是Google公司基于Protobuf 开发的跨语言的开源RPC框架

GRPC基于HTTP/2 协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好。

目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.

1、gRPC 是什么?

在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。

与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。

在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。

gRPC 客户端和服务端可以在多种环境中运行和交互 - 从 google 内部的服务器到你自己的笔记本,并且可以用任何 gRPC 支持的语言来编写。

所以,你可以很容易地用 Java 创建一个 gRPC 服务端,用 Go、Python、Ruby 来创建客户端。

此外,Google 最新 API 将有 gRPC 版本的接口,使你很容易地将 Google 的功能集成到你的应用里。

使用 protocol buffers

gRPC 默认使用 protocol buffers,这是 Google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)。

正如你将在下方例子里所看到的,你用 proto files 创建 gRPC 服务,用 protocol buffers 消息类型来定义方法参数和返回类型。

你可以在 Protocol Buffers 文档找到更多关于 Protocol Buffers 的资料。

Protocol buffers 版本

尽管 protocol buffers 对于开源用户来说已经存在了一段时间,例子内使用的却一种名叫 proto3 的新风格的 protocol buffers,它拥有轻量简化的语法、一些有用的新功能,并且支持更多新语言。

当前针对 Java 和 C++ 发布了 beta 版本,针对 JavaNano(即 Android Java)发布 alpha 版本,在protocol buffers Github 源码库里有 Ruby 支持, 在golang/protobuf Github 源码库里还有针对 Go 语言的生成器, 对更多语言的支持正在开发中。 你可以在 proto3 语言指南里找到更多内容, 在与当前默认版本的发布说明比较,看到两者的主要不同点。

更多关于 proto3 的文档很快就会出现。虽然你可以使用 proto2 (当前默认的 protocol buffers 版本), 我们通常建议你在 gRPC 里使用 proto3,因为这样你可以使用 gRPC 支持全部范围的的语言,并且能避免 proto2 客户端与 proto3 服务端交互时出现的兼容性问题,反之亦然。

再详细了解使用GRPC之前先来了解一下上面定义中的一些关键词。

首先我们来看一下HTTP/2是什么内容?

其实本质上就是http2.0版本,http目前为止主要有四个版本,分别为http1.0、http1.1、http2.0、https。

http1.0是最原始的版本,不支持持久连接,效率也比较低

http1.1针对http1.0版本做了优化,可以连接一次,多次使用,效率比http1.0高

http2.0实现了多路复用,对http1.1再次进行了优化。http2.0也被称为下一代http协议,是在2013年8月进行首次测试,所以现在用的不是很广。

https其实是在http协议上多加了一层SSL协议,具体如下图:

所以本质上,http1.0、http1.1、http2.0都可以添加SSL协议。

2、grpc环境安装

1)安装grpc包

bash 复制代码
go get -u google.golang.org/protobuf
go install google.golang.org/protobuf/cmd/protoc-gen-go
go get google.golang.org/grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc

安装gogofaster,用于加速编译

bash 复制代码
go get github.com/gogo/protobuf
go install github.com/gogo/protobuf/protoc-gen-gogofaster

2)生成grpc代码

最常见的有两种方法:

bash 复制代码
官方版本:protoc-go_out=.--go-grpc_out=.student.proto  //除了生成student.pb.go文件外还会生成student_grpc.pb.go
早些年使用: github.com/golang/protobuf/protoc-gen-go时,使用的命令是: protoc--go_out=plugins=grpc:. student.proto

gogofaster版本:

bash 复制代码
protoc --gogofaster_out=./micro_service/grpc  --proto_path=./micro_service/grpc student.proto    生成的go代码仅对message完成序列化,不包含service
protoc--gogofaster_out=plugins=grpc:. student.proto  //包含对service的序列化。只生成student.pb.go一个文件

3、grpc的使用

如果从Protobuf的角度看,GRPC只不过是一个针对service接口生成代码的生成器。接着我们来学习一下GRPC的用法。这里我们创建一个简单的proto文件,定义一个HelloService接口:

1)定义proto文件

bash 复制代码
syntax = "proto3";  //定义proto版本
package pb;

//消息体 一个package中不允许有两个同名的message
message Doctor{
  int32 age = 1;
  string name = 2;

}

//定义服务
service SayName{
  rpc SayHello(Doctor) returns (Doctor);
}

2)生成grpc代码

我们使用gogofaster编译

bash 复制代码
protoc -I="F:/goworks\src/jingtian/myrpc" --gogofaster_out=plugins=grpc:./pb/mygrpc --proto_path=./pb/mygrpc/ person.proto

追求极致的性能,使用gogofaster。

如果业务逻辑比较耗时,序列化数据不大,建议使用官方的序列化方式

查看生成的grpc代码

3)编写grpc服务端代码

生成的grpc代码中,生成了注册函数

服务端代码:

go 复制代码
package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    pb "jingtian/myrpc/pb/mygrpc"
    "net"
)

// Children 定义类
type Children struct {
}

// SayHello 类方法
func (children *Children) SayHello(ctx context.Context, doctor *pb.Doctor) (*pb.Doctor, error) {
    doctor.Name += "is Sleeping"
    return doctor, nil
}
func main() {
    //服务端创建步骤
    //1. 初始一个 grpc 对象
    //func NewServer(opt ...ServerOption) *Server
    grpcserver := grpc.NewServer()
    //2. 注册服务
    //之前,我们是直接自己注册,现在通过proto文件生成的代码中,生成了注册函数
    pb.RegisterSayNameServer(grpcserver, new(Children))

    //3. 设置监听, 指定 IP、port
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic("监听失败: " + err.Error())
    }
    fmt.Println("开始监听...")
    defer listener.Close()
    //4. 启动服务。---- serve()
    //这里就不需要再去建立conn连接了。直接通过grpcserver.Serve(listener)启动服务
    //func (s *Server) Serve(lis net.Listener) error
    err = grpcserver.Serve(listener)
    if err != nil {
        panic("grpcserver启动报错: " + err.Error())
    }

}

4)编写grpc客户端

bash 复制代码
package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    pb "jingtian/myrpc/pb/mygrpc"
    "time"
)

func main() {
    //1. 连接 grpc 服务,新版的不能用grpc.Dial了。使用grpc.NewClient
    conn, err := grpc.NewClient("127.0.0.1:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    //2. 初始化 grpc 客户端
    client := pb.NewSayNameClient(conn)
    //3. 调用远程服务。
    // 执行RPC调用并打印收到的响应数据,指定1秒超时
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    //创建Doctor对象,用来传参
    var doctor pb.Doctor
    doctor.Name = "景天"
    doctor.Age = 18
    resp, err := client.SayHello(ctx, &doctor)
    if err != nil {
        panic(err)
    }
    fmt.Println("从grpc服务端获取的数据: ", resp.Name)
}

如果我们不加 grpc.WithTransportCredentials(insecure.NewCredentials())。会报错

4、grpc其他特性

1)同步阻塞

我们关掉grpcserver。然后运行客户端

我们发现报错的地方,跟我们预想的不一样,这是因为连接函数是异步执行的

如果想把异步方式改为同步的话,只需要在连接的时候,加个参数grpc.WithBlock()

WithBlock() 只生效于 DialContext(),不会影响普通 grpc.Dial()。

必须搭配 context.WithTimeout(),否则可能永远卡住。

grpc.WithInsecure() 在新版本中建议替换为 grpc.WithTransportCredentials(insecure.NewCredentials())(如果不加密):

go 复制代码
package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    pb "jingtian/myrpc/pb/mygrpc"
    "time"
)

func main() {
    // 执行RPC调用并打印收到的响应数据,指定1秒超时
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    //1. 连接 grpc 服务,新版的不能用grpc.Dial了。使用grpc.NewClient
    conn, err := grpc.DialContext(
        ctx,
        "127.0.0.1:8080",
        grpc.WithTransportCredentials(insecure.NewCredentials()),
        grpc.WithBlock(), //直到连接真正建立才会返回,否则连接是异步建立的。因此grpc.WithBlock()和Timeout结合使用才有意义。server端正常的情况下使用grpc.WithBlock()得到的connection.GetState()为READY,不使用grpc.WithBlock()得到的connection.GetState()为IDEL
    )
    if err != nil {
        panic("建立连接失败: " + err.Error())
    }

    defer conn.Close()
    //2. 初始化 grpc 客户端
    client := pb.NewSayNameClient(conn)
    //3. 调用远程服务。

    //创建Doctor对象,用来传参
    var doctor pb.Doctor
    doctor.Name = "景天"
    doctor.Age = 18
    resp, err := client.SayHello(ctx, &doctor)
    if err != nil {
        panic("调用服务端函数失败: " + err.Error())
    }
    fmt.Println(resp.Name)
}

此时,不启动server,运行客户端

就会报错建立连接失败

2)设置收发数据大小

grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(10<<20), grpc.MaxCallRecvMsgSize(10<<20))

默认情况下SendMsg上限是MaxInt32,RecvMsg上限是4M,这里都修改为10M

3)grpc recover

大多数时候,服务端在开发的时候,是正常的,但是线上通常会因为客户端传递一些异常的参数,可能会导致panic的产生

这种情况很可能在测试时,也很难覆盖到。

因此,为了避免服务端运行时异常panic,我们需要捕捉recover下,在每个接口一进来,就写一个recover

go 复制代码
package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    pb "jingtian/myrpc/pb/mygrpc"
    "net"
)

// Children 定义类
type Children struct {
}

// SayHello 类方法
func (children *Children) SayHello(ctx context.Context, doctor *pb.Doctor) (*pb.Doctor, error) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    doctor.Name += " is Sleeping"
    return doctor, nil
}
func main() {
    //服务端创建步骤
    //1. 初始一个 grpc 对象
    //func NewServer(opt ...ServerOption) *Server
    grpcserver := grpc.NewServer()
    //2. 注册服务
    //之前,我们是直接自己注册,现在通过proto文件生成的代码中,生成了注册函数
    pb.RegisterSayNameServer(grpcserver, new(Children))

    //3. 设置监听, 指定 IP、port
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic("监听失败: " + err.Error())
    }
    fmt.Println("开始监听...")
    defer listener.Close()
    //4. 启动服务。---- serve()
    //这里就不需要再去建立conn连接了。直接通过grpcserver.Serve(listener)启动服务
    //func (s *Server) Serve(lis net.Listener) error
    err = grpcserver.Serve(listener)
    if err != nil {
        panic("grpcserver启动报错: " + err.Error())
    }

}

4)grpc拦截器

1. 服务端拦截器

如果使用多个拦截器,需要下载第三方包

go get github.com/grpc-ecosystem/go-grpc-middleware

go 复制代码
// 计时拦截器
func timerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface {
}, error) {
    begin := time.Now()
    resp, err := handler(ctx, req)
    fmt.Printf("%s finished in %d ms\n", info.FullMethod, time.Since(begin).Microseconds())
    return resp, err
}

// 限流拦截器。比如同时限制10个请求
var limitAtServer = make(chan struct{}, 10)

func limitInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    limitAtServer <- struct{}{}
    resp, err := handler(ctx, req)
    fmt.Printf("concurrency %d\n", len(limitAtServer)) //打印瞬时并发
    <-limitAtServer
    return resp, err
}

在创建grpc服务的时候,把拦截器传进去
    //使用中间件。默认grpc.NewServer()只支持一个中间件
    grpcserver := grpc.NewServer(
        //grpc.UnaryInterceptor(timerInterceptor), //grpc.UnaryInterceptor只能使用一次,即server端只能用一个拦截器
        //grpcmiddleware可以把多个拦截器,封装成一个拦截器
        grpc.UnaryInterceptor(grpcmiddleware.ChainUnaryServer(timerInterceptor, limitInterceptor)),
    )
2. 客户端拦截器

客户端也可以加,比如计时拦截器,客户端访问很多个接口,可以随时了解,访问的接口的快与慢

go 复制代码
var limitAtClient = make(chan struct{}, 10) //瞬间并发度限制为10

// 计时拦截器
func timerInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    begin := time.Now()
    err := invoker(ctx, method, req, reply, cc, opts...)
    fmt.Printf("use time %d ms\n", time.Since(begin).Milliseconds())
    return err
}

// 限流拦截器
func limitInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    limitAtClient <- struct{}{}
    err := invoker(ctx, method, req, reply, cc, opts...)
    fmt.Printf("concurrency %d\n", len(limitAtClient)) //打印瞬间并发度
    <-limitAtClient
    return err
}


应用
    // 连接到GRPC服务端
    conn, err := grpc.Dial("127.0.0.1:5678",
        grpc.WithTransportCredentials(insecure.NewCredentials()), //无需使用安全传输
        //在GRPC客户端使用拦截器
        grpc.WithChainUnaryInterceptor(timerInterceptor, limitInterceptor), //不定长参数,第一个拦截器在最外层,最后一个拦截器最靠近真实的业务调用
    )

5、grpc安全与认证

服务端与客户端之间数据传输进行加密认证

证书有两种方式

  1. 在公司内部,小组与小组之间互相调用接口可以使用自签证书
  2. 跨公司,跨外网,就需要购买证书了

1. go v1.15以下版本

我们采用自签证书形式

在go1.15版本以下可以采用以下方式:

1.生成server的私钥(由私钥可以生成公钥)

需要先安装openssl

openssl genrsa -out server.key 2048

2.生成自签名证书,有效其10年。证书里包含server的公钥和签发者2.信息

openssl req -x509 -new -nodes -key server.key -subj "/CN=localhost" -days 3650 -out server.crt

/CN指定Common Name,实际中替换成自己网站的域名在内网环境,server的证书可以直接传送给client。在公网环境下需要由CA统一管理所有的证书。

grpc数据安全传输

Server端代码:

go 复制代码
creds, err := credentials.NewServerTLSFromFile("server.crt", server.key")server:= grpc.NewServer(grpc.Creds(creds))

Client端代码:

go 复制代码
creds, err := credentials.NewClientTLSFromFile("server.crt", "")conn, err := grpc.Dial("localhost:5678",grpc.WithTransportCredentials(creds),)

Client端从CA拿到server的证书server.crt,证书里包含了server的公钥。

在TLS握手阶段,Client先生成自己的密钥(对称加密算法),用server的公钥加密自己的密钥,然后发给Server。

Server用自己的私钥(server.key)解密后获得Client的密钥。

此后所有的grpc数据传输都会先使用Client的密钥进行加密

2. go v1.15以后版本

在go v1.15以后,如果使用一般的证书通信,则会报错:

bash 复制代码
rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: tls: failed to verify certificate: x509: cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs"

总的来说,就是:

身份验证失败了,因为GO 1.15以后X509 不能用了,提示我们有两个选择:

  1. 需要使用SAN 证书
  2. 改变环境变量:GODEBUG=x509ignoreCN=0

这里,我们选择使用SAN证书,进行通信

SAN and openssl

首先,我们看看什么是SAN证书:

SAN(Subject Alternative Name) 是 SSL 标准 x509 中定义的一个扩展。使用了 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。

1)生成SAN证书

要生成SAN证书,需要以下几步:

  1. 修改openssl.cfg
    如果是Linux系统的话,应该修改的是openssl.cnf文件,我这个是windos,所以就是openssl.cfg。
    将openssl.cfg文件拷贝到需要生成证书的那个目录,进行修改。将一些默认注释的参数取消注释

1)打开copy_extensions 在CA_default节

bash 复制代码
# Extension copying option: use with caution.
copy_extensions = copy

2)打开req_extensions 在req中修改

bash 复制代码
req_extensions = v3_req # The extensions to add to a certificate request

这段配置表示在生成 CSR 文件时读取名叫 v3_req 的段落的配置信息,因此我们再在此配置文件中加入一段名为 v3_req 的配置:

3)增加subjectAltName 在v3_req里面

bash 复制代码
[ v3_req ]
...
subjectAltName = @alt_names
## 这段配置中最重要的是在最后导入名为 alt_names 的配置段,
## 因此我们还需要添加一个名为 [ alt_names ] 的配置段,这可以定义多个服务
[alt_names]
DNS.1 = *.org.example.com 
DNS.2 = *.example.com
2) 生成根证书

现在,开始生成证书,首先是生成CA证书:

生成CA私钥(.key)-->生成CA证书请求(.csr)-->自签名得到根证书(.crt)(CA给自已颁发的证书)。

生成根证书私钥

bash 复制代码
openssl genrsa -out ca.key 2048 

生成CA证书请求

bash 复制代码
openssl req -new -key ca.key -out ca.csr -subj "/C=cn/OU=myorg/O=mytest/CN=myname"

自签名得到根证书

bash 复制代码
openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.crt

其中:

genrsa:使用RSA算法产生私钥

-in:要输入的csr文件

-out:输出文件的路径

-subj:证书相关的用户信息(subject的缩写)

-key:指定私钥路径

-new:新证书签发请求

-req:输入csr文件

-days:证书的有效期(天)

3) 生成SNA的服务端证书

生成服务端私钥(serve.key)-->生成服务端证书请求(server.csr)-->CA对服务端请求文件签名,生成服务端证书(server.pem)

生成服务端证书私钥

bash 复制代码
openssl genrsa -out server.key 2048

根据私钥server.key生成证书请求文件server.crt

bash 复制代码
openssl req -new -nodes -key server.key -out server.crt -subj "/C=cn/OU=myserver/O=servercomp/CN=servername"  -config /etc/pki/tls/openssl.cnf -extensions v3_req

请求CA对证书请求文件签名,生成最终证书文件

bash 复制代码
openssl x509 -req -days 365 -in server.crt -out server.pem -CA ca.crt -CAkey ca.key -CAcreateserial  -extfile  /etc/pki/tls/openssl.cnf -extensions v3_req
Signature ok
subject=C = cn, OU = myserver, O = servercomp, CN = servername
Getting CA Private Key

验证:

bash 复制代码
openssl x509 -noout -text -in server.pem
[root@redis01 cert ]#openssl x509 -noout -text -in server.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            82:af:4a:7d:58:5f:9f:02
...
        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:FALSE
            X509v3 Key Usage: 
                Digital Signature, Non Repudiation, Key Encipherment
            X509v3 Subject Alternative Name: 
                DNS:*.org.example.com, DNS:*.example.com

生成的证书

3. grpc加密代码实现

1)server端
go 复制代码
package main

import (
    "context"
    "fmt"
    grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    pb "jingtian/myrpc/pb/mygrpc"
    "net"
    "time"
)

// Children 定义类
type Children struct {
}

// SayHello 类方法
func (children *Children) SayHello(ctx context.Context, doctor *pb.Doctor) (*pb.Doctor, error) {
    //recover捕获异常
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    doctor.Name += " is Sleeping"
    return doctor, nil
}

// 计时拦截器
func timerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface {
}, error) {
    begin := time.Now()
    resp, err := handler(ctx, req)
    fmt.Printf("%s finished in %d ms\n", info.FullMethod, time.Since(begin).Microseconds())
    return resp, err
}

// 限流拦截器。比如同时限制10个请求
var limitAtServer = make(chan struct{}, 10)

func limitInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    limitAtServer <- struct{}{}
    resp, err := handler(ctx, req)
    fmt.Printf("concurrency %d\n", len(limitAtServer)) //打印瞬时并发
    <-limitAtServer
    return resp, err
}
func main() {
    //grpc加密传输
    creds, err := credentials.NewServerTLSFromFile("server.pem", "server.key")
    if err != nil {
        fmt.Println("credentials.NewServerTLSFromFile err:", err)
    }
    fmt.Println("加密后是啥:", creds.Info())
    //服务端创建步骤
    //1. 初始一个 grpc 对象
    //func NewServer(opt ...ServerOption) *Server

    //使用中间件。默认grpc.NewServer()只支持一个中间件
    grpcserver := grpc.NewServer(
        //应用证书
        grpc.Creds(creds),
        //grpc.UnaryInterceptor(timerInterceptor), //grpc.UnaryInterceptor只能使用一次,即server端只能用一个拦截器
        //grpcmiddleware可以把多个拦截器,封装成一个拦截器
        grpc.UnaryInterceptor(grpcmiddleware.ChainUnaryServer(timerInterceptor, limitInterceptor)),
    )
    //2. 注册服务
    //之前,我们是直接自己注册,现在通过proto文件生成的代码中,生成了注册函数
    pb.RegisterSayNameServer(grpcserver, new(Children))

    //3. 设置监听, 指定 IP、port
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic("监听失败: " + err.Error())
    }
    fmt.Println("开始监听...")
    defer listener.Close()
    //4. 启动服务。---- serve()
    //这里就不需要再去建立conn连接了。直接通过grpcserver.Serve(listener)启动服务
    //func (s *Server) Serve(lis net.Listener) error
    err = grpcserver.Serve(listener)
    if err != nil {
        panic("grpcserver启动报错: " + err.Error())
    }

}
4)客户端
go 复制代码
package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    pb "jingtian/myrpc/pb/mygrpc"
    "time"
)

func main() {

    //证书加密传输 注意,第二个参数serverNameOverride 为服务名称。是我们在openssl配置文件中配置的,不能乱填
    creds, err := credentials.NewClientTLSFromFile("server.pem", "jingtian.example.com")
    if err != nil {
        fmt.Println("证书文件没找到", err)
    }
    // 执行RPC调用并打印收到的响应数据,指定1秒超时
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    //1. 连接 grpc 服务,新版的不能用grpc.Dial了。使用grpc.NewClient
    conn, err := grpc.NewClient(
        //ctx,
        "127.0.0.1:8080",
        //加密传输
        grpc.WithTransportCredentials(creds),
        //grpc.WithTransportCredentials(insecure.NewCredentials()),
        //grpc.WithBlock(), //直到连接真正建立才会返回,否则连接是异步建立的。因此grpc.WithBlock()和Timeout结合使用才有意义。server端正常的情况下使用grpc.WithBlock()得到的connection.GetState()为READY,不使用grpc.WithBlock()得到的connection.GetState()为IDEL
        grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(10<<20), grpc.MaxCallRecvMsgSize(10<<20)), //默认情况下SendMsg上限是MaxInt32,RecvMsg上限是4M,这里都修改为10M

    )
    if err != nil {
        panic("建立连接失败: " + err.Error())
    }

    defer conn.Close()
    //2. 初始化 grpc 客户端
    client := pb.NewSayNameClient(conn)
    //3. 调用远程服务。

    //创建Doctor对象,用来传参
    var doctor pb.Doctor
    doctor.Name = "景天"
    doctor.Age = 18
    resp, err := client.SayHello(ctx, &doctor)
    if err != nil {
        panic("调用服务端函数失败: " + err.Error())
    }
    fmt.Println(resp.Name)
}

运行服务端,客户端,拿到数据,加密传输成功

相关推荐
秋风&萧瑟7 分钟前
【QT】练习1
开发语言·qt·命令模式
东雁西飞9 分钟前
MATLAB 控制系统设计与仿真 - 33
开发语言·算法·matlab·机器人·自动控制
闪电麦坤9526 分钟前
C#:尝试解析方法TryParse
开发语言·c#
我不是程序猿儿28 分钟前
【C#】构造协议帧通过串口下发
开发语言·c#
千野竹之卫1 小时前
2025最新云渲染网渲100渲染农场使用方法,渲染100邀请码1a12
开发语言·前端·javascript·数码相机·3d·3dsmax
滴答滴答嗒嗒滴1 小时前
用 Python 实现机器学习小项目:从入门到实战
开发语言·python·机器学习
Julian.zhou1 小时前
MCP服务:五分钟实现微服务治理革命,无缝整合Nacos/Zookeeper/OpenResty
人工智能·微服务·zookeeper·交互·openresty
byxdaz1 小时前
Qt中绘制不规则控件
开发语言·qt
繁星蓝雨1 小时前
Qt使用QGraphicsView绘制线路图————附带详细实现代码
开发语言·qt
martian6651 小时前
《Spring Boot全栈开发指南:从入门到生产实践》
java·开发语言·spring boot