这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记
3 gRPC基础
每次需要获取的依赖:
go get -u google.golang.org/grpc
go get -u google.golang.org/protobuf
3.1 gRPC入门
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
//永久性获取
首先在项目文件夹下编写hello.proto
syntax = "proto3";
package helloService;
//在此文件中创建文件夹proto并将生成的文件保存到其中
option go_package = "./proto";
//传输的信息结构
message String {
string value = 1;
}
//服务
service HelloService {
rpc Hello (String) returns (String);
}
生成gRPC相关的代码(一定要cd到根目录):
protoc --go_out=. ./源文件名.proto
protoc --go-grpc_out=. ./源文件名.proto
会生成两个文件,重点关注*_grpc.pb.go的文件,我们调用此文件中的方法。
编写服务端的代码如下:
package main
import (
"RPC_Protobuf/together_helloServe/proto"
"context"
"google.golang.org/grpc"
"log"
"net"
)
//封装对象实现包外调用--满足.proto文件中***Service的接口
type Service struct {}
//实现helloService服务(根据之前的proto文件和提示写)
func (s *Service)Hello(ctx context.Context, reply *proto.String)(*proto.String,error) {
//实现业务逻辑
return &proto.String{
Value: "Hello "+ reply.Value,
},nil
}
func main() {
listener, err := net.Listen("tcp","localhost:1234")
if err!=nil{
log.Fatal(err)
}
grpcServer := grpc.NewServer()
//注册服务
proto.RegisterHelloServiceServer(grpcServer,new(Service))
err = grpcServer.Serve(listener)
if err!=nil{
log.Fatal(err)
}
}
再编写客户端的代码:
package main
import (
"RPC_Protobuf/together_helloServe/proto"
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
)
func main() {
//链接
//grpc.WithTransportCredentials(insecure.NewCredentials())跳过证书认证
conn, err := grpc.Dial("localhost:1234",grpc.WithTransportCredentials(insecure.NewCredentials()))
if err!= nil{
log.Fatal(err)
}
defer conn.Close()
//创建客户端
client := proto.NewHelloServiceClient(conn)
//调用服务
reply, err := client.Hello(context.Background(),&proto.String{Value: "TR"})
if err != nil {
log.Fatal(err)
}
//输出结果
fmt.Println(reply.GetValue())
}
其中grpc.Dial负责和gRPC服务建立链接,然后NewHelloServiceClient函数基于已经建立的链接构造HelloServiceClient对象。返回的client其实是一个HelloServiceClient接口对象,通过接口定义的方法就可以调用服务端对应的gRPC服务提供的方法。
3.2 gRPC流
RPC是远程函数调用,因此每次调用的函数参数和返回值不能太大,gRPC框架针对服务器端和客户端分别提供了流特性。
只需要再.proto文件中添加channel用于传输即可:
service HelloService {
rpc Hello (String) returns (String);
rpc Channel (stream String) returns (stream String);
//关键字stream指定启用流特性,参数部分是接收客户端参数的流,返回值是返回给客户端的流。
}
在生成的文件中可以发现服务端和客户端的流辅助接口均定义了Send和Recv方法用于流数据的双向通信
package main
import (
"RPC_Protobuf/together_stream/proto"
"context"
"fmt"
"google.golang.org/grpc"
"io"
"log"
"net"
)
type Service struct {
proto.UnimplementedHelloServiceServer
}
func (p *Service) Hello (stx context.Context, reply *proto.String) (*proto.String, error) {
fmt.Println("receive link form client: " + reply.GetValue())
return &proto.String{Value: reply.GetValue() + " received"},nil
}
func (p *Service) Channel(stream proto.HelloService_ChannelServer) error {
for {
args, err := stream.Recv()
if err != nil {
if err == io.EOF {
fmt.Println("传输完成")
return nil
}
return err
}
fmt.Println("Received from client: " + args.GetValue())
err = stream.Send(&proto.String{Value: args.GetValue() + " Received;" })
if err != nil {
return err
}
}
}
func main() {
lis, err := net.Listen("tcp","localhost:1234")
if err!=nil{
log.Fatal(err)
}
service := grpc.NewServer()
proto.RegisterHelloServiceServer(service,new(Service))
err = service.Serve(lis)
if err != nil {
log.Fatalf("grpcServer.Serve err: %v", err)
}
}
如果在.proto的文件中出现了mustEmbedUnimplementedHelloServiceServer() ,可以在我们自己的接口中嵌套UnimplementedHelloServiceServer的接口对象,即可跳过此函数的实现。
package main
import (
"RPC_Protobuf/together_stream/proto"
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
"sync"
"time"
)
func main() {
conn, err := grpc.Dial("localhost:1234",grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
client := proto.NewHelloServiceClient(conn)
stream, err := client.Channel(context.Background())
if err != nil {
log.Fatal(err)
}
var wg sync.WaitGroup
wg.Add(2)
go func(wg *sync.WaitGroup) {
defer wg.Done()
for{
err := stream.Send(&proto.String{Value: "请求链接"})
if err !=nil{
log.Fatal(err)
}
time.Sleep(time.Second*2)
}
}(&wg)
go func(wg *sync.WaitGroup) {
defer wg.Done()
for{
res, err := stream.Recv()
if err !=nil{
log.Fatal(err)
}
fmt.Println(res)
}
}(&wg)
wg.Wait()
}