【Go語言】RPC 使用指南(初學者版)

RPC(Remote Procedure Call,遠程過程調用)是一種計算機通信協議,允許程序調用另一臺計算機上的子程序,就像調用本地程序一樣。Go 語言內置了 RPC 支持,下面我會詳細介紹如何使用。

1

一、基本概念

在 Go 中,RPC 主要通過 net/rpc 包實現,它使用 Gob 編碼進行數據傳輸。Go 還提供了 net/rpc/jsonrpc 包,支持 JSON 編碼的 RPC。

二、最簡單的 RPC 示例

1. 定義服務

首先需要定義一個服務類型及其方法:

package mainimport ("errors""log""net""net/rpc"
)// 定義服務結構體
type Arith struct{}// 定義服務方法
// 注意:方法必須滿足以下條件:
// 1. 方法是導出的(首字母大寫)
// 2. 有兩個參數,都是導出類型或內建類型
// 3. 第二個參數是指針
// 4. 返回 error 類型
func (t *Arith) Multiply(args *Args, reply *int) error {*reply = args.A * args.Breturn nil
}func (t *Arith) Divide(args *Args, quo *Quotient) error {if args.B == 0 {return errors.New("divide by zero")}quo.Quo = args.A / args.Bquo.Rem = args.A % args.Breturn nil
}// 定義參數結構體
type Args struct {A, B int
}// 定義返回結構體
type Quotient struct {Quo, Rem int
}

2. 啟動 RPC 服務器

func main() {// 創建服務實例arith := new(Arith)// 注冊服務rpc.Register(arith)// 注冊服務到HTTP處理器(可選)// rpc.HandleHTTP()// 監聽TCP連接l, err := net.Listen("tcp", ":1234")if err != nil {log.Fatal("listen error:", err)}// 開始接受連接for {conn, err := l.Accept()if err != nil {log.Fatal("accept error:", err)}// 為每個連接創建goroutine處理go rpc.ServeConn(conn)}// 如果使用HTTP,可以這樣啟動:// http.ListenAndServe(":1234", nil)
}

3. 創建 RPC 客戶端

package mainimport ("log""net/rpc"
)// 定義參數結構體
type Args struct {A, B int
}// 定義返回結構體
type Quotient struct {Quo, Rem int
}func main() {// 連接RPC服務器client, err := rpc.Dial("tcp", "localhost:1234")if err != nil {log.Fatal("dialing:", err)}// 同步調用args := &Args{7, 8}var reply interr = client.Call("Arith.Multiply", args, &reply)if err != nil {log.Fatal("arith error:", err)}log.Printf("Arith: %d*%d=%d", args.A, args.B, reply)// 異步調用quotient := new(Quotient)divCall := client.Go("Arith.Divide", args, quotient, nil)replyCall := <-divCall.Done // 等待完成if replyCall.Error != nil {log.Fatal("arith error:", replyCall.Error)}log.Printf("Arith: %d/%d=%d...%d", args.A, args.B, quotient.Quo, quotient.Rem)
}

三、JSON-RPC 示例

如果你想使用 JSON 編碼而不是 Gob 編碼:

服務器端

func main() {arith := new(Arith)rpc.Register(arith)l, err := net.Listen("tcp", ":1234")if err != nil {log.Fatal("listen error:", err)}for {conn, err := l.Accept()if err != nil {log.Fatal("accept error:", err)}// 使用JSON編碼go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))}
}

客戶端

func main() {conn, err := net.Dial("tcp", "localhost:1234")if err != nil {log.Fatal("dial error:", err)}client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))args := &Args{7, 8}var reply interr = client.Call("Arith.Multiply", args, &reply)if err != nil {log.Fatal("arith error:", err)}log.Printf("Arith: %d*%d=%d", args.A, args.B, reply)
}

四、HTTP 上的 RPC

服務器端

func main() {arith := new(Arith)rpc.Register(arith)rpc.HandleHTTP()err := http.ListenAndServe(":1234", nil)if err != nil {log.Fatal("listen error:", err)}
}

客戶端

func main() {client, err := rpc.DialHTTP("tcp", "localhost:1234")if err != nil {log.Fatal("dialing:", err)}args := &Args{7, 8}var reply interr = client.Call("Arith.Multiply", args, &reply)if err != nil {log.Fatal("arith error:", err)}log.Printf("Arith: %d*%d=%d", args.A, args.B, reply)
}

五、更現代的 gRPC

Go 的標準 RPC 包功能有限,Google 開發的 gRPC 是更現代的 RPC 框架:

1. 安裝 gRPC

go get -u google.golang.org/grpc
go get -u github.com/golang/protobuf/protoc-gen-go

2. 定義 proto 文件

創建 hello.proto:

syntax = "proto3";package hello;service Greeter {rpc SayHello (HelloRequest) returns (HelloReply) {}
}message HelloRequest {string name = 1;
}message HelloReply {string message = 1;
}

3. 生成代碼

protoc --go_out=plugins=grpc:. hello.proto

4. 實現服務端

package mainimport ("context""log""net""google.golang.org/grpc"pb "path/to/your/package" // 替換為你的包路徑
)type server struct{}func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}func main() {lis, err := net.Listen("tcp", ":50051")if err != nil {log.Fatalf("failed to listen: %v", err)}s := grpc.NewServer()pb.RegisterGreeterServer(s, &server{})if err := s.Serve(lis); err != nil {log.Fatalf("failed to serve: %v", err)}
}

5. 實現客戶端

package mainimport ("context""log""os""time""google.golang.org/grpc"pb "path/to/your/package" // 替換為你的包路徑
)func main() {conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())if err != nil {log.Fatalf("did not connect: %v", err)}defer conn.Close()c := pb.NewGreeterClient(conn)name := "world"if len(os.Args) > 1 {name = os.Args[1]}ctx, cancel := context.WithTimeout(context.Background(), time.Second)defer cancel()r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})if err != nil {log.Fatalf("could not greet: %v", err)}log.Printf("Greeting: %s", r.Message)
}

六、選擇建議

  1. 標準庫 RPC:簡單、輕量,適合內部服務通信
  2. JSON-RPC:需要跨語言通信時使用
  3. gRPC:現代、高性能、支持多種語言,適合生產環境

七、常見問題

  1. 方法不滿足要求:確保方法簽名符合要求(兩個參數,第二個是指針,返回 error)
  2. 連接問題:檢查服務器是否啟動,端口是否正確

在Go RPC客戶端中設置超時時間

在Go語言的net/rpc包中,客戶端默認沒有直接提供設置超時時間的接口,但可以通過以下幾種方式實現超時控制:

1. 使用net.DialTimeout創建連接

在創建RPC客戶端連接時,可以使用net.DialTimeout代替net.Dial來設置連接超時:

func createClientWithTimeout() (*rpc.Client, error) {// 設置連接超時時間為5秒conn, err := net.DialTimeout("tcp", "localhost:1234", 5*time.Second)if err != nil {return nil, err}// 對于普通RPCclient := rpc.NewClient(conn)// 對于JSON-RPC// client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))return client, nil
}

2. 使用context實現調用超時

對于RPC調用本身的超時控制,可以使用context包:

func callWithTimeout(client *rpc.Client) {ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)defer cancel()args := &Args{7, 8}var reply int// 使用channel來接收結果ch := make(chan error, 1)go func() {ch <- client.Call("Arith.Multiply", args, &reply)}()select {case <-ctx.Done():fmt.Println("RPC調用超時:", ctx.Err())// 這里可以添加清理邏輯case err := <-ch:if err != nil {fmt.Println("RPC調用錯誤:", err)return}fmt.Printf("結果: %d\n", reply)}
}

3. 使用time.After實現超時

如果不使用context,也可以使用time.After實現類似的超時控制:

func callWithTimeoutAlt(client *rpc.Client) {args := &Args{7, 8}var reply intdone := make(chan error, 1)go func() {done <- client.Call("Arith.Multiply", args, &reply)}()select {case <-time.After(3 * time.Second):fmt.Println("RPC調用超時")case err := <-done:if err != nil {fmt.Println("RPC調用錯誤:", err)return}fmt.Printf("結果: %d\n", reply)}
}

4. 對于HTTP RPC的超時設置

如果使用HTTP作為傳輸協議,可以設置http.Client的超時:

func createHTTPClientWithTimeout() (*rpc.Client, error) {// 創建自定義HTTP客戶端并設置超時httpClient := &http.Client{Timeout: 5 * time.Second,}// 使用自定義HTTP客戶端創建RPC連接client, err := rpc.DialHTTPWithClient("tcp", "localhost:1234", httpClient)if err != nil {return nil, err}return client, nil
}

最佳實踐

  1. 同時設置連接超時和調用超時:連接超時和調用超時針對不同階段的問題
  2. 合理設置超時時間:根據網絡環境和業務需求設置合適的超時時間
  3. 超時后清理資源:確保超時后關閉連接或取消操作
  4. 記錄超時日志:記錄超時事件以便分析和優化

完整示例

package mainimport ("context""fmt""net""net/rpc""time"
)func main() {// 創建帶超時的客戶端client, err := createClientWithTimeout()if err != nil {fmt.Println("創建客戶端失敗:", err)return}defer client.Close()// 帶超時的RPC調用callWithTimeout(client)
}func createClientWithTimeout() (*rpc.Client, error) {// 5秒連接超時conn, err := net.DialTimeout("tcp", "localhost:1234", 5*time.Second)if err != nil {return nil, err}return rpc.NewClient(conn), nil
}func callWithTimeout(client *rpc.Client) {ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)defer cancel()args := &Args{7, 8}var reply intch := make(chan error, 1)go func() {ch <- client.Call("Arith.Multiply", args, &reply)}()select {case <-ctx.Done():fmt.Println("RPC調用超時:", ctx.Err())case err := <-ch:if err != nil {fmt.Println("RPC調用錯誤:", err)return}fmt.Printf("結果: %d\n", reply)}
}type Args struct {A, B int
}

通過以上方法,你可以有效地控制RPC客戶端的超時行為,提高系統的健壯性和可靠性。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/78388.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/78388.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/78388.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

11、Refs:直接操控元素——React 19 DOM操作秘籍

一、元素操控的魔法本質 "Refs是巫師與麻瓜世界的連接通道&#xff0c;讓開發者能像操控魔杖般精準控制DOM元素&#xff01;"魔杖工坊的奧利凡德先生輕撫著魔杖&#xff0c;React/Vue的refs能量在杖尖躍動。 ——以神秘事務司的量子糾纏理論為基&#xff0c;揭示DOM…

MinIO 教程:從入門到Spring Boot集成

文章目錄 一. MinIO 簡介1. 什么是MinIO&#xff1f;2. 應用場景 二. 文件系統存儲發展史1. 服務器磁盤&#xff08;本地存儲&#xff09;2. 分布式文件系統(如 HDFS、Ceph、GlusterFS)3. 對象存儲&#xff08;如 MinIO、AWS S3&#xff09;4.對比總結5.選型建議6.示例方案 三.…

電競俱樂部護航點單小程序,和平地鐵俱樂部點單系統,三角洲護航小程序,暗區突圍俱樂部小程序

電競俱樂部護航點單小程序開發&#xff0c;和平地鐵俱樂部點單系統&#xff0c;三角洲護航小程序&#xff0c;暗區突圍俱樂部小程序開發 端口包含&#xff1a; 超管后臺&#xff0c; 老板端&#xff0c;打手端&#xff0c;商家端&#xff0c;客服端&#xff0c;管事端&#x…

基于 IPMI + Kickstart + Jenkins 的 OS 自動化安裝

Author&#xff1a;Arsen Date&#xff1a;2025/04/26 目錄 環境要求實現步驟自定義 ISO安裝 ipmitool安裝 NFS定義 ks.cfg安裝 HTTP編寫 Pipeline 功能驗證 環境要求 目標服務器支持 IPMI / Redfish 遠程管理&#xff08;如 DELL iDRAC、HPE iLO、華為 iBMC&#xff09;&…

如何在SpringBoot中通過@Value注入Map和List并使用YAML配置?

在SpringBoot開發中&#xff0c;我們經常需要從配置文件中讀取各種參數。對于簡單的字符串或數值&#xff0c;直接使用Value注解就可以了。但當我們需要注入更復雜的數據結構&#xff0c;比如Map或者List時&#xff0c;該怎么操作呢&#xff1f;特別是使用YAML這種更人性化的配…

短信驗證碼安全實戰:三網API+多語言適配開發指南

在短信服務中&#xff0c;創建自定義簽名是發送通知、驗證信息和其他類型消息的重要步驟。萬維易源提供的“三網短信驗證碼”API為開發者和企業提供了高效、便捷的自定義簽名創建服務&#xff0c;可以通過簡單的接口調用提交簽名給運營商審核。本文將詳細介紹如何使用該API&…

RabbitMQ和Seata沖突嗎?Seata與Spring中的事務管理沖突嗎

1. GlobalTransactional 和 Transactional 是否沖突&#xff1f; 答&#xff1a;不沖突&#xff0c;它們可以協同工作&#xff0c;但作用域不同。 Transactional: 這是 Spring 提供的注解&#xff0c;用于管理單個數據源內的本地事務。在你當前的 register 方法中&#xff0c…

一臺服務器已經有個python3.11版本了,如何手動安裝 Python 3.10,兩個版本共存

環境&#xff1a; debian12.8 python3.11 python3.10 問題描述&#xff1a; 一臺服務器已經有個python3.11版本了&#xff0c;如何手動安裝 Python 3.10&#xff0c;兩個版本共存 解決方案&#xff1a; 1.下載 Python 3.10 源碼&#xff1a; wget https://www.python.or…

c++中的enum變量 和 constexpr說明符

author: hjjdebug date: 2025年 04月 23日 星期三 13:40:21 CST description: c中的enum變量 和 constexpr說明符 文章目錄 1.Q:enum 類型變量可以有,--操作嗎&#xff1f;1.1補充: c/c中enum的另一個細微差別. 2.Q: constexpr 修飾的函數,要求傳入的參數必需是常量嗎&#xff…

postman工具

postman工具 進入postman官網 www.postman.com/downloads/ https://www.postman.com/downloads/ https://www.postman.com/postman/published-postman-templates/documentation/ae2ja6x/postman-echo?ctxdocumentation Postman Echo is a service you can use to test your …

Spring和Spring Boot集成MyBatis的完整對比示例,包含從項目創建到測試的全流程代碼

以下是Spring和Spring Boot集成MyBatis的完整對比示例&#xff0c;包含從項目創建到測試的全流程代碼&#xff1a; 一、Spring集成MyBatis示例 1. 項目結構 spring-mybatis-demo/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com.example/…

【數據可視化-24】巧克力銷售數據的多維度可視化分析

?? 博主簡介:曾任某智慧城市類企業算法總監,目前在美國市場的物流公司從事高級算法工程師一職,深耕人工智能領域,精通python數據挖掘、可視化、機器學習等,發表過AI相關的專利并多次在AI類比賽中獲獎。CSDN人工智能領域的優質創作者,提供AI相關的技術咨詢、項目開發和個…

c語言-分支結構

以下是我初學C語言的筆記記錄&#xff0c;歡迎留言補充 一&#xff0c;分支結構分為幾個 兩個&#xff0c;一個是if語句&#xff0c;一個是Switch語句 二&#xff0c;if語句 &#xff08;1&#xff09;結構體 int main() {if()//判斷條件{//表達式}else if()//判斷條件{//表達式…

數據庫MySQL學習——day4(更多查詢操作與更新數據)

文章目錄 1、聚合函數&#xff08;Aggregate Functions&#xff09;2、分組查詢&#xff08;GROUP BY&#xff09;3、更新數據&#xff08;UPDATE&#xff09;4、刪除數據&#xff08;DELETE&#xff09;5、進階練習示例6、 今日小結 1、聚合函數&#xff08;Aggregate Functio…

Spark-SQL 項目

一、項目概述 &#xff08;一&#xff09;實驗目標 統計有效數據條數&#xff1a;篩選出uid、phone、addr三個字段均無空值的記錄并計數。提取用戶數量最多的前 20 個地址&#xff1a;按地址分組統計用戶數&#xff0c;按降序排序后取前 20 名。 &#xff08;二&#xff09;…

Redis的ZSet對象底層原理——跳表

我們來聊聊「跳表&#xff08;Skip List&#xff09;」&#xff0c;這是一個既經典又優雅的數據結構&#xff0c;尤其在 Redis 中非常重要&#xff0c;比如 ZSet&#xff08;有序集合&#xff09;底層就用到了跳表。 &#x1f31f; 跳表&#xff08;Skip List&#xff09;簡介 …

2025深圳中興通訊安卓開發社招面經

2月27號 中興通訊一面 30多分鐘 自我介紹 聊項目 我的優缺點&#xff0c;跟同事相比&#xff0c;有什么突出的地方 Handler機制&#xff0c;如何判斷是哪個消息比較耗時 設計模式&#xff1a;模板模式 線程的狀態 線程的開啟方式 線程池原理 活動的啟動模式 Service和Activity…

【Castle-X機器人】二、智能導覽模塊安裝與調試

持續更新。。。。。。。。。。。。。。。 【Castle-X機器人】智能導覽模塊安裝與調試 二、智能導覽模塊安裝與調試2.1 智能導覽模塊安裝2.2 智能導覽模塊調試2.2.1 紅外測溫傳感器測試2.2.2 2D攝像頭測試 二、智能導覽模塊安裝與調試 2.1 智能導覽模塊安裝 使用相應工具將智能…

深入理解二叉樹遍歷:遞歸與棧的雙重視角

二叉樹的遍歷前序遍歷中序遍歷后續遍歷總結 二叉樹的遍歷 雖然用遞歸的方法遍歷二叉樹實現起來更簡單&#xff0c;但是要想深入理解二叉樹的遍歷&#xff0c;我們還必須要掌握用棧遍歷二叉樹&#xff0c;遞歸其實就是利用了系統棧去遍歷。特此記錄一下如何用雙重視角去看待二叉…

Qt Creator中自定義應用程序的可執行文件圖標

要在Qt Creator中為你的應用程序設置自定義可執行文件圖標&#xff0c;你需要按照以下步驟操作&#xff1a; Windows平臺設置方法 準備圖標文件&#xff1a; 創建一個.ico格式的圖標文件&#xff08;推薦使用256x256像素&#xff0c;包含多種尺寸&#xff09; 可以使用在線工…