RPC(Remote Procedure Call, 遠程過程調用) 是一種計算機通信協議, 它允許程序調用另一個地址空間(通常是遠程機器上的)的過程或函數, 就像本地調用一樣, 而不需要顯示地處理網絡通信的細節。RPC使得分布式系統中的不同模塊能夠相互通信, 而對開發者而言, 這種通信就像是本地調用一樣簡單。其調用原理圖如下:
圖片
上面的步驟看起來很復雜, 實際上, 在當前的主流RPC框架, 例如:grpc、thrift, 只需要關心第1步和最后1步即可, 中間過程已經由框架進行了封裝。在這篇文章中, 將從學習的角度自己來實現一個RPC的服務端全流程。
這里模擬了用戶信息的注冊管理流程, 在服務端, 保存有多個用戶信息, 并提供兩個遠程調用接口, 一個是通過ID獲取對應的用戶信息接口, 另一個是添加新的用戶。參考代碼如下:
// 1. 定義要遠程調用的方法type MathService struct {}func (m *MathService) Multiply(args *Args, reply *int) error { *reply = args.A * args.B return nil}// 2. 定義請求和響應的數據結構type Args struct { A, B int}
在上面的代碼中, 我們定義了一個MathService結構體,其中包含了一個Multiply方法,該方法用于實現兩個整數相乘的遠程調用。接下來我們需要完成服務端的服務端口監聽和連接建立, 參考代碼如下:
func main() { mathService := new(MathService) rpc.Register(mathService) listener, err := net.Listen("tcp", ":1234") if err != nil { log.Fatal("Listen error:", err) } for { conn, err := listener.Accept() if err != nil { log.Fatal("Accept error:", err) } go rpc.ServeConn(conn) }}
在上面的main函數中, 我們注冊了一個MathService服務,并在本地監聽1234端口,當接收到客戶端連接后, 使用rpc.ServeConn來處理RPC請求。
客戶端連接服務端的參考代碼如下:
type Args struct { A, B int}func main() { client, err := rpc.Dial("tcp", "localhost:1234") if err != nil { log.Fatal("Dial error:", err) } args := &Args{7, 8} var reply int err = client.Call("MathService.Multiply", args, &reply) if err != nil { log.Fatal("MathService.Multiply error:", err) } fmt.Printf("MathService.Multiply: %d * %d = %d/n", args.A, args.B, reply)}
先編譯服務端代碼并啟動, 然后執行客戶端程序,結果如下:
圖片
客戶端成功調用了服務端的遠程函數并收到結果。
gRPC(gRPC Remote Procedure Calls)是由Google開發的開源RPC(Remote Procedure Call,遠程過程調用)框架,其目標是在跨網絡的服務之間實現高效的通信。gRPC使用Protocol Buffers(protobuf)作為其接口描述語言,并支持多種編程語言,包括C++, Java, Python, Go, Node.js等。
首先需要安裝gRPC相關的包,通過以下命令安裝:
go get -u google.golang.org/grpc
接著需要安裝Protocol Buffers工具, 可以從這里直接下載最新版, 根據自己的操作系統類型選擇:
https://github.com/protocolbuffers/protobuf/releases
接下來需要安裝兩個包:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latestgo install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
緊接著創建proto文件, 內容如下:
syntax = "proto3";package grpcsample;option go_package = ".";message User { string id = 1; string name = 2; int32 age = 3;}service UserService { rpc GetUserById (UserRequest) returns (User); rpc AddUser (User) returns (User);}message UserRequest { string id = 1;}
將上面的代碼生成文件, 文件名為: user.proto。
我這里將protoc二進制程序放到工程根目錄gosample下, 接著在命令行下輸入以下命令:
./protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./grpcsample/user.proto
該命令將把.proto文件內容生成對應的grpc Go代碼, 生成后將在grpcsample目錄下生成user.pb.go和user_grpc.pb.go文件,如圖:
圖片
在grpcsample目錄下新建工程文件userservice.go, 加入以下代碼:
import ( "context" "fmt")var users = map[string]User{ "1": {Id: "1", Name: "John Doe", Age: 30}, "2": {Id: "2", Name: "Jane Doe", Age: 25},}type UserServer struct { UnimplementedUserServiceServer}func (s *UserServer) GetUserById(ctx context.Context, req *UserRequest) (*User, error) { user, exists := users[req.Id] if exists { return &user, nil } return nil, fmt.Errorf("User with ID %s not found", req.Id)}func (s *UserServer) AddUser(ctx context.Context, user *User) (*User, error) { users[user.Id] = *user return user, nil}
上面的代碼提供了兩個RPC方法, GetUserById支持通過ID查詢對應的用戶信息, AddUser支持添加一個新的用戶。
接著添加服務端的主程序代碼:
import ( "google.golang.org/grpc" pb "gosample/grpcsample")func main() { listener, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("Failed to listen: %v", err) } server := grpc.NewServer() pb.RegisterUserServiceServer(server, &pb.UserServer{}) log.Println("gRPC server is running on port 50051") if err := server.Serve(listener); err != nil { log.Fatalf("Failed to serve: %v", err) }}
在服務端主程序代碼中, 我們調用了grpcsample中的RegisterUserServiceServer方法注冊了一個服務,并在本地的50051端口監聽客戶端連接。
同樣的方式, 新打開一個工程, 按照服務端生成gRPC的方式生成客戶端的代碼, 如圖:
圖片
在客戶端的主程序中利用如下代碼進行服務端方法調用:
package mainimport ( "context" "fmt" "log" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "sampleclient/grpcsample")func main() { conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalf("Failed to connect: %v", err) } defer conn.Close() client := pb.NewUserServiceClient(conn) // 通過ID查詢用戶 user, err := client.GetUserById(context.Background(), &pb.UserRequest{Id: "1"}) if err != nil { log.Fatalf("Error getting user: %v", err) } fmt.Printf("User: %+v/n", user) // 添加一個新用戶 newUser := &pb.User{Id: "3", Name: "Alice", Age: 28} addedUser, err := client.AddUser(context.Background(), newUser) if err != nil { log.Fatalf("Error adding user: %v", err) } fmt.Printf("Added User: %+v/n", addedUser)}
在上面的代碼中, 首先通過grpc包中的Dial函數連接到本地50051端口, 并調用gRPC的方法NewUserServiceClient新建一個客戶端連接, 接著遠程調用了服務端的兩個方法。首先開啟服務端, 查看客戶端調用方法后的返回,如圖:
圖片
可以看到,成功獲取到遠程的兩個方法返回的結果。
本文鏈接:http://www.www897cc.com/showinfo-26-56409-0.html利用Go傳統RPC和gRPC框架分別實現一個RPC服務端
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com