35-grpc各种调用方式

19 阅读4分钟

目录如下:

image.png

proto文件书写

  • 根目录执行 生成 hello.pb.go 文件
    • protoc -I . --go_out=. --go_opt=paths=source_relative ./helloworld/proto/hello.proto
  • 根目录执行 生成 hello_grpc.pb.go 文件
    • protoc -I . --go-grpc_out=. --go-grpc_opt=paths=source_relative ./helloworld/proto/hello.proto
syntax = "proto3";

// 命名空间防止命名冲突
package helloworld;
//生成代码的引用路径
option go_package = "grpc/helloworld/proto";

import "google/protobuf/timestamp.proto";// 引入其他proto文件,时间类型
import "google/protobuf/any.proto";// go语言的interface类型,可以包含任意类型

service Greeter {
    // 一元调用 (适合小数据量)
    rpc SayHello (HelloRequest) returns (HelloReply) {}

    // 客户端流式调用 (适合文件上传)
    rpc SayHelloClientStream (stream HelloRequest) returns (HelloReply) {}

    // 服务端流式调用 (适合文件下载)
    rpc SayHelloStream (HelloRequest) returns (stream HelloReply) {}

    // 双向流式调用 (适合实时通信)
    rpc SayHelloBothStream (stream HelloRequest) returns (stream HelloReply) {}
}

enum Gender {
    // 从0开始
    MALE = 0;
    FEMALE = 1;
    THIRD = 2;
    reserved 3; // 也可使用保留字段
}

message Address {
    string provice = 1;
    string city = 2;
}

message HelloRequest {
    // 小技巧: 使用的字段多的排前面
    // 1开始 1-15: 占一个字节
    string name = 1;
    Gender gender = 2;
    uint32 age = 3;
    google.protobuf.Timestamp birthday = 4;    // 时间类型
    Address addr = 5;                           //自定义类型
    repeated string hobys = 6;                 // 定义数组/切片
    map<string, google.protobuf.Any> data = 7; // 定义map
    reserved 8, 9 ,18 to 50;                     // 保留数字字段
    reserved "phone", "email";                 // 保留字段名
}

message HelloReply {
    string message = 1;
}

// 根目录执行 生成 hello.pb.go 文件
// protoc -I . --go_out=. --go_opt=paths=source_relative ./helloworld/proto/hello.proto
// 根目录执行 生成 hello_grpc.pb.go 文件
// protoc -I . --go-grpc_out=. --go-grpc_opt=paths=source_relative ./helloworld/proto/hello.proto

服务端代码

  • go run .\helloworld\server\server.go 执行命令
package main

import (
	"context"
	"flag"
	"fmt"
	"grpc/helloworld/proto"
	"io"
	"log"
	"net"
	"time"

	"google.golang.org/grpc"
)

// 设置端口号为50051
var (
	port = flag.Int("port", 50051, "端口号")
)

type server struct {
	proto.UnimplementedGreeterServer // 没实现的接口也能使用
}

// 实现 SayHello 方法
func (s *server) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	return &proto.HelloReply{Message: "Hello " + in.GetName()}, nil
}

// 客户端流式 RPC
func (s *server) SayHelloClientStream(stream proto.Greeter_SayHelloClientStreamServer) error {
	i := 0
	for {
		req, err := stream.Recv() // 接收客户端发送的数据
		if err == io.EOF {        // 接收完毕
			return stream.SendAndClose(&proto.HelloReply{Message: fmt.Sprintf("total: %d", i)})
		}
		if err != nil {
			log.Printf("stream.Recv() error: %v", err)
			return err
		}
		log.Printf("Received: %v", req.GetName())
		i++
	}
}

// 服务端流式 RPC
func (s *server) SayHelloStream(in *proto.HelloRequest, stream proto.Greeter_SayHelloStreamServer) error {
	log.Printf("Received request: %v", in.GetName())

	// 模拟发送多条消息
	messages := []string{
		fmt.Sprintf("Hello %s!", in.GetName()),
		fmt.Sprintf("Welcome to gRPC streaming, %s!", in.GetName()),
		fmt.Sprintf("This is message 3 for %s!", in.GetName()),
		fmt.Sprintf("Last message for %s!", in.GetName()),
	}

	// 逐条发送消息
	for _, msg := range messages {
		// 模拟处理延迟
		time.Sleep(time.Second)

		// 发送消息
		if err := stream.Send(&proto.HelloReply{Message: msg}); err != nil {
			log.Printf("Failed to send message: %v", err)
			return err
		}

		log.Printf("Sent message: %s", msg)
	}

	return nil
}

// 双向流式 RPC
func (s *server) SayHelloBothStream(stream proto.Greeter_SayHelloBothStreamServer) error {
	i := 0
	for {
		req, err := stream.Recv() // 接收客户端发送的数据
		if err == io.EOF {        // 接收完毕
			break
		}
		if err != nil {
			log.Printf("stream.Recv() error: %v", err)
			return err
		}
		log.Printf("Received: %v\n", req)
		// 发送数据给客户端
		err = stream.Send(&proto.HelloReply{Message: fmt.Sprintf("%d", i)})
		if err != nil {
			log.Printf("stream.Send() error: %v", err)
			return err
		}
		i++
	}
	return nil
}

func main() {
	flag.Parse() // 解析命令行参数
	//server端
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
		return
	}
	s := grpc.NewServer()                     // 创建gRPC服务
	proto.RegisterGreeterServer(s, &server{}) // 注册服务
	err = s.Serve(lis)
	if err != nil {
		log.Fatalf("failed to serve: %v", err)
		return
	}
	fmt.Println("server start")
}

// go run .\helloworld\server\server.go 执行命令

客户端代码

  • go run .\helloworld\client\client.go 执行命令
package main

import (
	"context"
	"flag"
	"fmt"
	"grpc/helloworld/proto"
	"io"
	"log"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/protobuf/types/known/anypb"
	"google.golang.org/protobuf/types/known/timestamppb"
)

var (
	addr = flag.String("addr", "localhost:50051", "server address") // 获取服务端的端口
)

func main() {
	flag.Parse()
	// 设置连接超时
	// ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	// defer cancel()

	// 客户端实现
	// conn, err := grpc.DialContext(ctx, *addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatal(err)
	}

	defer conn.Close() // 关闭连接

	// 创建客户端
	c := proto.NewGreeterClient(conn)
	sayHello(c)           // 调用服务端的方法
	sayHelloStream(c)     // 调用客户端流的方法
	SayHelloBothStream(c) // 调用双向流的方法
	SayHelloStream(c)     // 调用服务端流的方法
}

func getHelloRequest() *proto.HelloRequest {
	birthday := timestamppb.New(time.Now()) // 创建生日
	any1, _ := anypb.New(birthday)          // 必须使用proto里面定义的类型,否则会报错
	in := &proto.HelloRequest{
		Name:     "jxb",
		Gender:   proto.Gender_FEMALE,
		Age:      18,
		Birthday: birthday,
		Hobys:    []string{"吃饭", "睡觉", "打豆豆"},
		Addr: &proto.Address{
			Provice: "湖南",
			City:    "永州",
		},
		Data: map[string]*anypb.Any{
			"a": any1,
		},
	} // 创建请求
	return in
}

// 正常调用
func sayHello(c proto.GreeterClient) {
	ctx := context.Background()     // 创建上下文
	in := getHelloRequest()         // 获取请求
	res, err := c.SayHello(ctx, in) // 调用服务端的方法
	if err != nil {
		log.Fatal(err)
		return
	}
	log.Println(res.Message) // 打印服务端返回的消息
}

// 客户端流式调用
func sayHelloStream(c proto.GreeterClient) {
	ctx := context.Background() // 创建上下文
	list := []*proto.HelloRequest{
		getHelloRequest(),
		getHelloRequest(),
		getHelloRequest(),
	} // 创建请求列表
	stream, err := c.SayHelloClientStream(ctx) // 调用服务端的方法
	if err != nil {
		log.Fatal(err)
		return
	}

	// 客户端持续发送流
	for _, in := range list {
		if err = stream.Send(in); err != nil {
			log.Fatal(err)
			return
		}
	}
	reply, err := stream.CloseAndRecv() // 关闭流并接收服务端返回的消息
	if err != nil {
		log.Fatal(err)
		return
	}
	log.Println("客户端流式调用返回消息:", reply.Message) // 打印服务端返回的消息
}

// 双向流式调用
func SayHelloBothStream(c proto.GreeterClient) {
	ctx := context.Background() // 创建上下文
	list := []*proto.HelloRequest{
		getHelloRequest(),
		getHelloRequest(),
		getHelloRequest(),
	} // 创建请求列表
	stream, err := c.SayHelloBothStream(ctx) // 调用服务端的方法
	if err != nil {
		log.Fatal(err)
		return
	}

	var done = make(chan struct{}, 0) // 创建一个通道用于接收服务端返回的消息

	// 客户端持续接收(用协程)
	go func() {
		for {
			repley, err := stream.Recv() // 接收请求
			if err == io.EOF {
				close(done) // 关闭通道
				return
			}
			if err != nil {
				log.Fatal(err)
				close(done) // 关闭通道
				return
			}
			fmt.Printf("client recv: %v\n", repley.Message)
		}
	}()

	// 客户端持续发送流
	for _, in := range list {
		if err = stream.Send(in); err != nil {
			log.Fatal(err)
			return
		}
	}

	err = stream.CloseSend() // 关闭客户端发送流
	<-done                   // 等待服务端返回消息

	if err != nil {
		log.Fatal(err)
		return
	}
}

// 服务端流式调用
func SayHelloStream(c proto.GreeterClient) {
	ctx := context.Background()                             // 创建上下文
	stream, err := c.SayHelloStream(ctx, getHelloRequest()) // 调用服务端的方法
	if err != nil {
		log.Fatal(err)
		return
	}

	// 创建一个通道来同步
	done := make(chan struct{})
	// 持续接收服务端流式响应
	go func() {
		defer close(done) // 确保退出时关闭通道
		for {
			reply, err := stream.Recv() // 接收响应
			if err == io.EOF {          // 流结束
				break
			}
			if err != nil {
				log.Fatal(err)
				return
			}
			fmt.Printf("client recv: %v\n", reply.Message)
		}
	}()
	<-done // 等待接收完成
	fmt.Println("Client stream processing completed")
}

// go run .\helloworld\client\client.go 执行命令
  • 服务端 image.png

  • 客户端 image.png