Go-grpc 實現

聽風走了八千里發表於2022-02-18

什麼是grpc和protobuf

grpc

​ grpc是一個Google開源的高效能、開源和通用的RPC框架,面向移動和HTTP/2設計。目前提供C、Java和Go語言版本, 分別是grpc, grpc-java 和 grpc-go, 其中C語言版本又支援C , C++,Node.js, Python, Ruby, Object-C, PHP, C#

grpc協議使用的序列化程式不是json 、xml 等, 而是使用的protobuf序列化及反序列化

protobuf

  • 習慣使用json、xml互動資料的人,大多沒有聽說過 Protocol Buffer
  • Protocol Buffer其實是Google出品的一種輕量&高效的結構化資料儲存格式,效能要比json、xml強很多
  • 目前主流使用的protobuf3

go get -d google.golang.org/protobuf/cmd/protoc-gen-go

安裝好protoc執行程式後 寫入.proto檔案

syntax = "proto3";
option go_package = ".;proto";

service Greeter{
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;

}

寫完之後 執行命令生成.go檔案protoc -I . first.proto --go_out=plugins=grpc:.

  • Server端程式碼
package main

import (
	"context"
	"log"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"

	pb "AwesomeMicroPro/MicroProject/proto"
)

// server is used to implement pb.GreeterServer .
type server struct {
	pb.UnimplementedGreeterServer
}


// SayHello 方法接受遠端呼叫方法
func (s *server) SayHello(ctx context.Context, request *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Println(request.Name + "remote 1 first")
	return &pb.HelloReply{Message: "hello " + request.Name}, nil
}


func main() {
	g := grpc.NewServer()
	reflection.Register(g)
	pb.RegisterGreeterServer(g, &server{})
	listen, err := net.Listen("tcp", "127.0.0.1:8080")
	if err != nil {
		panic("Listen Port Failed"+err.Error())
	}

	err = g.Serve(listen)
	if err != nil {
		panic("Server Enable Failed"+err.Error())
	}

}

  • Client端程式碼
package main

import (
	"context"
	"google.golang.org/grpc"
	"log"
	"os"

	pb "AwesomeMicroPro/MicroProject/proto"
)

const (
	address = "127.0.0.1:8080"
)

func main() {
	// 撥號連線Server
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	if err != nil {
		panic("Connect grpc Server Failed" + err.Error())
	}
	// 最後關閉連線
	defer conn.Close()

	client := pb.NewGreeterClient(conn)
	name := "testing ***"
	if len(os.Args) > 1 {
		name = os.Args[1]
	}
	// 遠端呼叫SayHello方法傳入對應的值
	reply, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: name})
	if err != nil {
		panic("fun remote execute failed" + err.Error())
	}

	log.Println(reply.Message)

}

rpc的四種模式

  • 簡單模式
    • 簡單模式最為傳統 和熟悉的資料流模式沒有任何區別,即客戶端請求一次資料 服務端相應一個資料
  • 服務端資料流模式
    • 客戶端發起一次請求 服務端連續不斷的返回資料流 stream, 典型的例子就是客戶端發起一個股票程式碼 服務端實時的將股票的資訊返回到客戶端
  • 客戶端資料流模式
    • 客戶端源源不斷的向服務端傳送資料流 ,傳送結束後 服務端返回一個響應資料, 例子 : 物聯網終端向伺服器報告資料
  • 雙向資料流模式
    • 客戶端和服務端都可以傳送資料流 、實時互動。 例子: 聊天

服務端資料流、客戶端資料流、雙向資料流 模式

stream.proto

syntax = "proto3";
option go_package = "./;proto";


service Greeter  {

  rpc GetStream (StreamReqData) returns (stream StreamResData) {} // 服務端流模式

  rpc PutStream (stream StreamReqData) returns (StreamResData) {} // 客戶端流模式

  rpc AllStream (stream StreamResData) returns (stream StreamReqData) {} // 雙向流模式
}

message StreamReqData {

  string data = 1;
}
message StreamResData {
  string data = 2;
}

protoc -I . stream.proto --go_out=plugins=grpc:.

server.go

package main

import (
	"AwesomeMicroPro/stream_grpc_test/proto"
	"fmt"
	"google.golang.org/grpc"
	"log"
	"net"
	"sync"
	"time"
)

const PORT = ":50052"


var wg sync.WaitGroup

type server struct {
}

// GetStream 服務端流模式 源源不斷的傳送資料給客戶端
func (s *server) GetStream(data *proto.StreamReqData, res proto.Greeter_GetStreamServer) error {

	for i := 0; i < 10; i++ {
		_ = res.Send(&proto.StreamResData{
			Data: fmt.Sprintf("%v", time.Now().Unix()),
		})
		time.Sleep(time.Second)
	}

	return nil
}

// PutStream 客戶端流模式  源源不斷的接受客戶端的請求
func (s *server) PutStream(putStreamServer proto.Greeter_PutStreamServer) error {
	for i:=0; i< 10; i ++{
		recv, err := putStreamServer.Recv()
		if err != nil {
			log.Println("Recv Data Failed"+ err.Error())
			break
		}
		fmt.Println(recv)
	}
	return nil
}

func (s *server) AllStream(allstream proto.Greeter_AllStreamServer) error {
	wg.Add(2)
	go func() {
		for {
			recv, err := allstream.Recv()
			if err != nil {
				log.Println("接受資料失敗")
				break
			}
			fmt.Println("服務端接收到的資料:"+recv.Data)
		}
		defer wg.Done()
	}()

	go func() {
		for  {
			err := allstream.Send(&proto.StreamReqData{
				Data: fmt.Sprintf("我是服務端%v", time.Now().Unix()),
			})
			if err != nil {
				log.Println("服務端傳送資料失敗" + err.Error())
				break
			}
			time.Sleep(time.Second)
		}
		wg.Done()
	}()

	wg.Wait()
	return nil
}

func main() {
	listen, err := net.Listen("tcp", PORT)
	if err != nil {
		log.Println("Listen tcp port failed" + err.Error())
	}
	newServer := grpc.NewServer()
	proto.RegisterGreeterServer(newServer, &server{})
	err = newServer.Serve(listen)
}

Client.go

package main

import (
	"AwesomeMicroPro/stream_grpc_test/proto"
	"context"
	"fmt"
	"google.golang.org/grpc"
	"log"
	"sync"
	"time"
)

var wg sync.WaitGroup

func main() {
	conn, err := grpc.Dial("localhost:50052", grpc.WithInsecure())
	if err != nil {
		log.Println("connect grpc service failed" + err.Error())
	}

	defer conn.Close()

	client := proto.NewGreeterClient(conn)
	// GetStream 服務端流模式  適合訂閱監控某些資訊
	stream, err := client.GetStream(context.Background(), &proto.StreamReqData{Data: "服務端資料流呼叫"})

	for i:=0; i < 10; i++{
		recv, err := stream.Recv()
		if err != nil {
			log.Println(err.Error())
		}
		log.Println(recv)
	}
	// 客戶端流模式
	putStream, err := client.PutStream(context.Background())
	for i:=0; i < 10; i ++ {
		err := putStream.Send(&proto.StreamReqData{Data: "**da **" + string(i)})
		if err != nil {
			log.Println("err" + err.Error())
			break
		}
	}

	// 雙向流模式
	allStream, err := client.AllStream(context.Background())
	wg.Add(2)
	go func() {
		for {
			recv, err := allStream.Recv()
			if err != nil {
				log.Println("接收資料失敗")
				break
			}
			fmt.Println("客戶端接收到的資料: " + recv.Data)
		}
		defer wg.Done()
		
	}()
	go func() {
		for {
			err := allStream.Send(&proto.StreamResData{Data: fmt.Sprintf("我是客戶端 %v", time.Now().Unix())})
			if err != nil {
				log.Println("客戶端傳送資料失敗"+err.Error())
				break
			}
			time.Sleep(time.Second)
		}
		wg.Done()
	}()
	wg.Wait()
}

相關文章