【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.mzph.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; 可以使用在线工…