首页 > 后端开发 > Golang > 正文

GolangRPC客户端与服务器完整示例

P粉602998670
发布: 2025-09-04 09:14:01
原创
860人浏览过
Golang的net/rpc包提供高效、强类型的RPC通信机制,适用于高性能微服务内部通信。通过定义共享接口(如Arith服务的Multiply方法),在服务器端注册服务并监听连接,客户端通过Dial建立连接后可同步或异步调用远程方法。相比RESTful API,RPC使用二进制编码(如gob),性能更高、延迟更低,适合对性能敏感的内部服务通信;而REST因基于HTTP、易于调试和跨语言兼容,更适合公共API。实现时需遵循方法导出、两个参数(请求和指针响应)、返回error等规则,并通过rpc.Register注册服务。生产环境中需结合context实现超时控制,合理处理错误(如自定义错误类型),复用rpc.Client实例以减少连接开销,并设计重连机制应对网络不稳,确保系统健壮性。

golangrpc客户端与服务器完整示例

Golang的

net/rpc
登录后复制
包提供了一种非常直观且高效的方式来构建远程过程调用(RPC)系统,它让不同进程甚至不同机器上的程序能够像调用本地函数一样互相通信。这种内置的机制,在我看来,是Go语言在并发和网络编程领域强大能力的又一体现,特别适合构建高性能的微服务内部通信。

解决方案

要搭建一个Golang RPC客户端与服务器的完整示例,我们需要三个核心部分:一个共享的接口定义(通常是结构体及其方法),一个服务器端实现,以及一个客户端调用。

首先,我们定义一个共享的服务接口。为了简单起见,我们创建一个

Arith
登录后复制
服务,它有一个
Multiply
登录后复制
方法。这个定义在客户端和服务器端都需要。

shared/arith.go
登录后复制
(共享定义)

立即学习go语言免费学习笔记(深入)”;

package shared

// Args 定义了乘法运算的两个操作数
type Args struct {
    A, B int
}

// Quotient 定义了除法运算的结果(如果我们要扩展的话)
type Quotient struct {
    Quo, Rem int
}

// Arith 是我们的RPC服务接口
type Arith int // 只是一个占位符,实际方法会绑定到这个类型上
登录后复制

接着,我们实现服务器端。服务器会注册

Arith
登录后复制
服务,并监听一个端口,等待客户端连接。

server/main.go
登录后复制
(服务器端)

package main

import (
    "log"
    "net"
    "net/rpc"
    "time"

    "your_module_path/shared" // 替换为你的模块路径
)

// Arith 是服务的实际实现
type Arith int

// Multiply 方法接收Args结构体作为参数,将结果写入reply,并返回error
func (t *Arith) Multiply(args *shared.Args, reply *int) error {
    log.Printf("Server received Multiply request: %d * %d", args.A, args.B)
    *reply = args.A * args.B
    time.Sleep(100 * time.Millisecond) // 模拟一些工作负载
    return nil
}

func main() {
    arith := new(Arith)
    rpc.Register(arith) // 注册服务实例

    tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
    if err != nil {
        log.Fatalf("Error resolving TCP address: %v", err)
    }

    listener, err := net.ListenTCP("tcp", tcpAddr)
    if err != nil {
        log.Fatalf("Error listening on port: %v", err)
    }
    log.Println("RPC Server started on port 1234")

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Printf("Accept error: %v", err)
            continue
        }
        // 为每个新连接启动一个goroutine来处理RPC请求
        go rpc.ServeConn(conn)
    }
}
登录后复制

最后,我们构建客户端,它会连接到服务器,并调用

Multiply
登录后复制
方法。

client/main.go
登录后复制
(客户端)

package main

import (
    "context"
    "log"
    "net/rpc"
    "time"

    "your_module_path/shared" // 替换为你的模块路径
)

func main() {
    client, err := rpc.Dial("tcp", "localhost:1234")
    if err != nil {
        log.Fatalf("Error dialing RPC server: %v", err)
    }
    defer client.Close()

    args := shared.Args{A: 7, B: 8}
    var reply int

    // 创建一个带超时的上下文
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    // 异步调用RPC,并在select中处理结果或超时
    call := client.Go("Arith.Multiply", args, &reply, nil)

    select {
    case <-call.Done:
        if call.Error != nil {
            log.Fatalf("RPC call error: %v", call.Error)
        }
        log.Printf("Arith: %d * %d = %d", args.A, args.B, reply)
    case <-ctx.Done():
        log.Printf("RPC call timed out: %v", ctx.Err())
    }

    // 再次调用,这次使用同步方式
    var reply2 int
    args2 := shared.Args{A: 10, B: 5}
    err = client.Call("Arith.Multiply", args2, &reply2)
    if err != nil {
        log.Fatalf("Synchronous RPC call error: %v", err)
    }
    log.Printf("Arith: %d * %d = %d", args2.A, args2.B, reply2)
}
登录后复制

要运行这个示例,你需要将

your_module_path
登录后复制
替换为你的Go模块路径(例如
github.com/yourusername/yourproject
登录后复制
),并在项目根目录运行
go mod init your_module_path
登录后复制
。然后先启动服务器,再启动客户端。你会看到服务器打印接收到的请求,客户端打印计算结果。

智慧车行预约小程序
智慧车行预约小程序

智慧车行小程序,是一个专门为洗车/4S/车辆维修行业打造的小程序,前后端完整代码包括车行动态,养车常识,保养预约,维修预约,洗车美容预约,汽车检测预约等功能。采用腾讯提供的小程序云开发解决方案,无须服务器和域名预约管理:开始/截止时间/人数均可灵活设置,可以自定义客户预约填写的数据项预约凭证:支持线下到场后校验签到/核销/二维码自助签到等多种方式详尽的预约数据:支持预约名单数据导出Excel,打印

智慧车行预约小程序 0
查看详情 智慧车行预约小程序

Golang RPC与RESTful API,我该如何选择?

这确实是一个老生常谈的问题,但每次我启动新项目,尤其涉及内部服务通信时,都会在脑海里过一遍。对我来说,选择Golang RPC还是RESTful API,很大程度上取决于服务的“受众”和“性能敏感度”。

RESTful API,以其无状态、易于理解和调试的特性,以及广泛的工具支持,无疑是构建公共API或与前端(Web/移动)交互的首选。它基于HTTP协议,请求和响应通常是JSON或XML,人类可读性极强。如果你需要一个对外开放、兼容性好的接口,或者你的服务需要被各种异构系统消费,那么REST几乎是你的不二之选。它的松耦合特性也意味着服务之间可以独立演进,不会因为接口变更而频繁牵连。

然而,当谈到微服务之间的内部通信,特别是对性能、延迟有较高要求,或者服务间数据传输量大时,Golang RPC的优势就显现出来了。

net/rpc
登录后复制
通常使用Go的
gob
登录后复制
编码,这是一种二进制协议,相比于JSON/HTTP,它的序列化和反序列化效率更高,数据包也更小。这意味着更低的网络开销和更快的处理速度。此外,RPC通常是强类型的,服务接口在编译时就能确定,减少了运行时因类型不匹配导致的错误,这对于大型、复杂的微服务系统来说,能带来更好的开发体验和维护性。我个人在构建一些后端计算密集型服务时,就更倾向于RPC,因为它能榨取出更多的性能潜力。当然,这种紧密的类型绑定也意味着服务间的耦合度相对较高,接口变更可能需要同步更新客户端,这需要团队在设计时就考虑清楚。

Golang RPC服务接口定义与注册的那些事儿

在Golang中定义RPC服务接口,其实比很多人想象的要简单,但也有一些约定俗成的规则需要遵守,否则你的方法就无法被RPC系统识别。我刚开始接触时,也踩过一些小坑,比如忘记将方法首字母大写,或者参数类型不对。

核心规则是:

  1. 方法必须是导出(Exported)的: 也就是说,方法名首字母必须大写。
  2. 方法必须有两个参数: 第一个参数是请求(request)类型,第二个参数是响应(response)类型。
  3. 第二个参数必须是指针类型: 这是因为RPC系统需要将结果写入这个参数。
  4. 方法必须返回一个
    error
    登录后复制
    类型:
    如果方法执行成功,返回
    nil
    登录后复制
    ;如果失败,返回具体的错误信息。

举个例子,我们上面的

Multiply
登录后复制
方法:
func (t *Arith) Multiply(args *shared.Args, reply *int) error
登录后复制
这里
t *Arith
登录后复制
是接收者,
args *shared.Args
登录后复制
是请求参数,
reply *int
登录后复制
是响应参数(指针),
error
登录后复制
是返回类型。

服务注册则是通过

rpc.Register(serviceInstance)
登录后复制
rpc.RegisterName(name, serviceInstance)
登录后复制
完成的。
rpc.Register
登录后复制
会使用服务实例的类型名(比如
Arith
登录后复制
)作为RPC服务的名称。而
rpc.RegisterName
登录后复制
允许你自定义服务的名称,这在某些情况下非常有用,比如当你需要注册多个相同类型的服务,或者想给服务一个更具描述性的名字时。注册之后,客户端就可以通过这个名称来调用服务的方法了,例如
client.Call("Arith.Multiply", ...)
登录后复制

理解这些规则和注册机制,是构建健壮Golang RPC服务的基石。它们确保了RPC框架能够正确地反射出你的方法,并进行参数的序列化与反序列化。

应对Golang RPC中的错误、超时与连接管理

在实际生产环境中,网络通信总是充满了不确定性。错误、超时和连接断开是常态,而不是异常。因此,在Golang RPC中,妥善处理这些问题至关重要,它直接关系到服务的健壮性和用户体验。

错误处理 Golang RPC方法的返回类型是

error
登录后复制
,这意味着每次RPC调用后,我们都应该检查这个错误。服务器端返回的任何非
nil
登录后复制
错误,都会在客户端的
client.Call
登录后复制
call.Error
登录后复制
中体现。这允许我们构建自定义的错误类型,以便客户端可以根据错误类型进行不同的处理。比如,定义一个
InvalidArgumentError
登录后复制
,当客户端传入无效参数时返回,客户端收到后就知道是参数问题,而不是网络问题

// 服务器端
func (t *Arith) Divide(args *shared.Args, reply *shared.Quotient) error {
    if args.B == 0 {
        return errors.New("divide by zero") // 返回自定义错误
    }
    // ...
    return nil
}

// 客户端
var quo shared.Quotient
err := client.Call("Arith.Divide", args, &quo)
if err != nil {
    if err.Error() == "divide by zero" {
        log.Println("Error: Cannot divide by zero!")
    } else {
        log.Printf("RPC call failed: %v", err)
    }
}
登录后复制

超时机制 超时是防止服务雪崩的关键。如果一个RPC调用长时间没有响应,它可能会阻塞客户端,并耗尽资源。在Golang RPC客户端中,我们可以利用

context
登录后复制
包来优雅地实现超时。通过
context.WithTimeout
登录后复制
创建一个带有超时的上下文,然后在异步RPC调用(
client.Go
登录后复制
)后,通过
select
登录后复制
语句监听
call.Done
登录后复制
通道和
ctx.Done
登录后复制
通道。哪个先触发,我们就处理哪个。

// 客户端示例(已在解决方案中给出)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

call := client.Go("Arith.Multiply", args, &reply, nil)

select {
case <-call.Done:
    // RPC调用完成,处理结果
case <-ctx.Done():
    // RPC调用超时
    log.Printf("RPC call timed out: %v", ctx.Err())
}
登录后复制

这种模式非常强大,它允许你在不阻塞主goroutine的情况下,同时管理RPC调用的完成和超时。

连接管理 RPC客户端与服务器的连接管理也需要细心。

rpc.Dial
登录后复制
会建立一个TCP连接,并将其封装成
rpc.Client
登录后复制
。在客户端,通常建议复用这个
rpc.Client
登录后复制
实例,而不是每次调用都重新建立连接,因为连接的建立是有开销的。如果连接断开(例如服务器重启),
client.Call
登录后复制
会返回错误。在生产系统中,我们通常会实现一个连接池或重连机制,当检测到连接断开时,尝试重新建立连接,或者从连接池中获取一个新的可用连接。这部分通常会涉及到一些更高级的设计模式,比如使用
sync.Pool
登录后复制
来管理
rpc.Client
登录后复制
实例,或者实现一个带有指数退避策略的重连器。服务器端则需要考虑优雅停机,确保在关闭前处理完所有正在进行的请求,避免客户端接收到意外的连接关闭错误。

以上就是GolangRPC客户端与服务器完整示例的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号