2026年3月12日 星期四

Golang, gRPC(proto buf)學習筆記

這似乎也是一們必修的課,不然json傳來傳去不香嗎


首先要建立資料型態 ./pb/simple.proto,為了辨識這副檔名,我安裝了vscode-proto3套件

Greeter名字可以自取,但在使用時例如在之後的server: pb.Unimplemented[自取姓名]Server , pb.Register[自取姓名]Server就會變成這樣

剩下就像interface定義輸入輸出模型

// 指定使用最新的 protobuf 第三版語法
syntax = "proto3";

// 定義 protobuf 內部的包裝名稱,避免跟別人寫的 message 命名衝突
package simple;

// 關鍵設定:告訴編譯器,生成的 Go 程式碼要放在哪一個 Go package 中
// 這裡我們指定生成到專案中 p5_grpc/simple/pb 這個 package
option go_package = "p5_grpc/simple/pb";

// 定義一個名為 Greeter 的「服務」(Service),就像是定一個 API 介面
service Greeter {
// 定義一個名為 SayHello 的 API 方法
// 它的輸入是 HelloRequest,輸出是 HelloReply
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// 定義客戶端傳給伺服器的資料結構 (請求)
message HelloRequest {
// 第一個欄位是字串型態,名稱為 name,數字 "1" 是它在二進位傳輸時的順序標籤
string name = 1;
}

// 定義伺服器回傳給客戶端的資料結構 (回應)
message HelloReply {
// 同樣地,定義一個字串欄位 message,順序標籤為 1
string message = 1;
}

在proto設定完成後還要輸入一段指令好產生simple.pb.go和simple_grpc.pb.go,千萬別傻傻的自己打,然後protoc指令不知道為什麼電腦理本來就有,不用特別裝什麼,改天沒有在研究吧


# 確保你已經有安裝 protoc 與 go 的 protoc 外掛
# 實際生成指令如下:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
simple_grpc/pb/simple.proto

接著要來建立server./server/main.go

package main

import (
"context"
"log"
"net"

// 匯入上一步工具幫我們自動產生的 pb package
pb "p5_grpc/simple/pb"

"google.golang.org/grpc"
)

// 定義一個我們自己的 sever 結構體
type server struct {
// 必須嵌入這個,這是 gRPC 為了向前相容的強制規定
pb.UnimplementedGreeterServer
}

// 正式實作 proto 中定義的 SayHello 方法!
// 注意:函數名稱和傳入傳出的參數型別,都必須跟自動生成的 pb 程式碼一致
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
// in.GetName() 可以安全地拿到客戶端傳來的 name 欄位
log.Printf("收到客戶端的請求名稱: %v", in.GetName())

// 把 "Hello " 加上客戶端傳來的名字,包裝成 HelloReply 回傳給客戶端
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
// 1. 在本地端的 50052 port 開啟一個 TCP 監聽器
lis, err := net.Listen("tcp", ":50052")
if err != nil {
log.Fatalf("無法開啟監聽: %v", err)
}

// 2. 建立一台全新的 gRPC 伺服器
s := grpc.NewServer()

// 3. 把我們自己寫的 server 邏輯結構體,註冊到 gRPC 伺服器上
pb.RegisterGreeterServer(s, &server{})
log.Printf("簡單版 gRPC Server 正在監聽: %v", lis.Addr())

// 4. 開始伺服器以接受客戶端連線 (程式會卡在這裡持續運行)
if err := s.Serve(lis); err != nil {
log.Fatalf("伺服器啟動失敗: %v", err)
}
}

最後再來個client就搞定了./client/main.go

package main

import (
"context"
"log"
"time"

// 同樣地,匯入自動產生的 pb package
pb "p5_grpc/simple/pb"

"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

func main() {
// 1. 設定並連接到目標伺服器的位置 (localhost:50052)
// grpc.WithTransportCredentials(insecure.NewCredentials()) 代表我們不使用 SSL/TLS 加密連線 (僅供本地測試用)
conn, err := grpc.Dial("localhost:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("無法連線至伺服器: %v", err)
}
// 確保程式結束前會關閉連線釋放資源
defer conn.Close()

// 2. 透過連線建立一個存取 Greeter 服務的客戶端實例
c := pb.NewGreeterClient(conn)

// 3. 建立一個有 1 秒超時限制的 Context
// 這是為了避免連線卡住,如果在 1 秒內沒完成就會自動取消
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

log.Println("準備呼叫 SayHello 方法...")

// 4. 透過客戶端實例呼叫遠端的 SayHello 方法,並把自己的名字 (Gopher) 當作參數傳過去
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "Gopher"})
if err != nil {
log.Fatalf("呼叫失敗: %v", err)
}

// 5. 成功收到回應後,印出伺服器回傳的內容 (Message 欄位)
log.Printf("收到伺服器回傳: %s", r.GetMessage())
}

最後就是相繼把server, client啟動,就會看到Hello Gopher。大概就是這樣吧,明天再來研究最後一堂課MQ




沒有留言:

張貼留言